blob: 7ff7a3976f68f37694dc1c61d96cd2f180d97811 [file] [log] [blame]
asharifec9c6242013-02-15 19:56:06 +00001#!/usr/bin/python2.6
2#
3# Copyright 2010 Google Inc. All Rights Reserved.
4
asharif0ea89582013-02-15 21:15:11 +00005__author__ = 'asharif@google.com (Ahmad Sharif)'
asharifec9c6242013-02-15 19:56:06 +00006
asharifec9c6242013-02-15 19:56:06 +00007import optparse
8import os
9import re
10import socket
11import sys
12import tempfile
asharif0ea89582013-02-15 21:15:11 +000013from automation.clients.helper import perforce
asharifec9c6242013-02-15 19:56:06 +000014from utils import command_executer
15from utils import logger
16from utils import utils
17
18
asharif1b92e732013-02-15 20:51:38 +000019def GetCanonicalMappings(mappings):
20 canonical_mappings = []
21 for mapping in mappings:
22 remote_path, local_path = mapping.split()
asharif0ea89582013-02-15 21:15:11 +000023 if local_path.endswith('/') and not remote_path.endswith('/'):
asharif1b92e732013-02-15 20:51:38 +000024 local_path = os.path.join(local_path, os.path.basename(remote_path))
25 remote_path = remote_path.lstrip('/').split('/', 1)[1]
26 canonical_mappings.append(perforce.PathMapping(remote_path, local_path))
27 return canonical_mappings
28
asharif0ea89582013-02-15 21:15:11 +000029
asharif56d78bc2013-02-15 21:15:08 +000030def SplitMapping(mapping):
asharif0ea89582013-02-15 21:15:11 +000031 parts = mapping.split()
32 assert len(parts) <= 2, 'Mapping %s invalid' % mapping
33 remote_path = parts[0]
34 if len(parts) == 2:
35 local_path = parts[1]
asharif56d78bc2013-02-15 21:15:08 +000036 else:
asharif0ea89582013-02-15 21:15:11 +000037 local_path = '.'
asharif56d78bc2013-02-15 21:15:08 +000038 return remote_path, local_path
39
asharif1b92e732013-02-15 20:51:38 +000040
41class Repo(object):
asharifec9c6242013-02-15 19:56:06 +000042 def __init__(self):
43 self.repo_type = None
44 self.address = None
45 self.mappings = None
46 self.revision = None
asharif0ea89582013-02-15 21:15:11 +000047 self.ignores = ['.gitignore', '.p4config', 'README.google']
asharif56d78bc2013-02-15 21:15:08 +000048 self._root_dir = tempfile.mkdtemp()
49 self._ce = command_executer.GetCommandExecuter()
50 self._logger = logger.GetLogger()
asharifec9c6242013-02-15 19:56:06 +000051
asharif56d78bc2013-02-15 21:15:08 +000052 def PullSources(self):
asharif0ea89582013-02-15 21:15:11 +000053 """Pull all sources into an internal dir."""
asharifec9c6242013-02-15 19:56:06 +000054 pass
55
asharif56d78bc2013-02-15 21:15:08 +000056 def SetupForPush(self):
asharif0ea89582013-02-15 21:15:11 +000057 """Setup a repository for pushing later."""
asharif56d78bc2013-02-15 21:15:08 +000058 pass
59
60 def PushSources(self, commit_message, dry_run=False):
asharif0ea89582013-02-15 21:15:11 +000061 """Push to the external repo with the commit message."""
asharif56d78bc2013-02-15 21:15:08 +000062 pass
63
64 def _RsyncExcludingRepoDirs(self, source_dir, dest_dir):
asharif0ea89582013-02-15 21:15:11 +000065 command = 'rsync -a --exclude=.git --exclude=.svn %s/ %s/' % (source_dir,
66 dest_dir)
asharif56d78bc2013-02-15 21:15:08 +000067 return self._ce.RunCommand(command)
68
69 def MapSources(self, dest_dir):
70 """Copy sources from the internal dir to root_dir."""
71 return self._RsyncExcludingRepoDirs(self._root_dir, dest_dir)
72
73 def GetRoot(self):
74 return self._root_dir
75
76 def CleanupRoot(self):
asharif0ea89582013-02-15 21:15:11 +000077 command = 'rm -rf %s' % self._root_dir
asharif56d78bc2013-02-15 21:15:08 +000078 return self._ce.RunCommand(command)
asharifec9c6242013-02-15 19:56:06 +000079
80 def __str__(self):
asharif0ea89582013-02-15 21:15:11 +000081 return '\n'.join(str(s) for s in [self.repo_type,
asharif1b92e732013-02-15 20:51:38 +000082 self.address,
83 self.mappings])
asharifec9c6242013-02-15 19:56:06 +000084
85
86class P4Repo(Repo):
87 def __init__(self, address, mappings):
88 Repo.__init__(self)
asharif0ea89582013-02-15 21:15:11 +000089 self.repo_type = 'p4'
asharifec9c6242013-02-15 19:56:06 +000090 self.address = address
91 self.mappings = mappings
92
asharif56d78bc2013-02-15 21:15:08 +000093 def PullSources(self):
asharifec9c6242013-02-15 19:56:06 +000094 client_name = socket.gethostname()
asharif0ea89582013-02-15 21:15:11 +000095 client_name += tempfile.mkstemp()[1].replace('/', '-')
asharifec9c6242013-02-15 19:56:06 +000096 mappings = self.mappings
asharif0ea89582013-02-15 21:15:11 +000097 p4view = perforce.View('depot2',
asharif1b92e732013-02-15 20:51:38 +000098 GetCanonicalMappings(mappings))
asharif0ea89582013-02-15 21:15:11 +000099 p4client = perforce.CommandsFactory(self._root_dir, p4view,
100 name=client_name)
asharif1b92e732013-02-15 20:51:38 +0000101 command = p4client.SetupAndDo(p4client.Sync())
asharif0ea89582013-02-15 21:15:11 +0000102 ret = self._ce.RunCommand(command)
103 assert ret == 0, 'Could not setup client.'
asharif1b92e732013-02-15 20:51:38 +0000104 command = p4client.InCheckoutDir(p4client.SaveCurrentCLNumber())
asharif0ea89582013-02-15 21:15:11 +0000105 ret, o, _ = self._ce.RunCommand(command, return_output=True)
106 assert ret == 0, 'Could not get version from client.'
107 self.revision = re.search('^\d+$', o.strip(), re.MULTILINE).group(0)
asharif1b92e732013-02-15 20:51:38 +0000108 command = p4client.InCheckoutDir(p4client.Remove())
asharif0ea89582013-02-15 21:15:11 +0000109 ret = self._ce.RunCommand(command)
110 assert ret == 0, 'Could not delete client.'
asharif1b92e732013-02-15 20:51:38 +0000111 return 0
asharifec9c6242013-02-15 19:56:06 +0000112
113
114class SvnRepo(Repo):
115 def __init__(self, address, mappings):
116 Repo.__init__(self)
asharif0ea89582013-02-15 21:15:11 +0000117 self.repo_type = 'svn'
asharifec9c6242013-02-15 19:56:06 +0000118 self.address = address
119 self.mappings = mappings
120
asharif56d78bc2013-02-15 21:15:08 +0000121 def PullSources(self):
asharif0ea89582013-02-15 21:15:11 +0000122 with utils.WorkingDirectory(self._root_dir):
asharif56d78bc2013-02-15 21:15:08 +0000123 for mapping in self.mappings:
124 remote_path, local_path = SplitMapping(mapping)
asharif0ea89582013-02-15 21:15:11 +0000125 command = 'svn co %s/%s %s' % (self.address, remote_path, local_path)
asharif56d78bc2013-02-15 21:15:08 +0000126 ret = self._ce.RunCommand(command)
asharifcdbacaf2013-02-15 20:09:23 +0000127 if ret: return ret
asharif56d78bc2013-02-15 21:15:08 +0000128
asharif0ea89582013-02-15 21:15:11 +0000129 self.revision = ''
asharif56d78bc2013-02-15 21:15:08 +0000130 for mapping in self.mappings:
131 remote_path, local_path = SplitMapping(mapping)
asharif0ea89582013-02-15 21:15:11 +0000132 command = 'cd %s && svnversion .' % (local_path)
133 ret, o, _ = self._ce.RunCommand(command, return_output=True)
asharif56d78bc2013-02-15 21:15:08 +0000134 self.revision += o.strip()
135 if ret: return ret
asharifcdbacaf2013-02-15 20:09:23 +0000136 return 0
asharifec9c6242013-02-15 19:56:06 +0000137
138
139class GitRepo(Repo):
asharif56d78bc2013-02-15 21:15:08 +0000140 def __init__(self, address, branch, mappings=None, ignores=None):
asharifec9c6242013-02-15 19:56:06 +0000141 Repo.__init__(self)
asharif0ea89582013-02-15 21:15:11 +0000142 self.repo_type = 'git'
asharifec9c6242013-02-15 19:56:06 +0000143 self.address = address
asharif0ea89582013-02-15 21:15:11 +0000144 self.branch = branch or 'master'
asharifec9c6242013-02-15 19:56:06 +0000145 if ignores:
146 self.ignores += ignores
asharif56d78bc2013-02-15 21:15:08 +0000147 self.mappings = mappings
asharifec9c6242013-02-15 19:56:06 +0000148
asharif56d78bc2013-02-15 21:15:08 +0000149 def _CloneSources(self):
asharif0ea89582013-02-15 21:15:11 +0000150 with utils.WorkingDirectory(self._root_dir):
151 command = 'git clone %s .' % (self.address)
asharif56d78bc2013-02-15 21:15:08 +0000152 return self._ce.RunCommand(command)
asharifec9c6242013-02-15 19:56:06 +0000153
asharif56d78bc2013-02-15 21:15:08 +0000154 def PullSources(self):
asharif0ea89582013-02-15 21:15:11 +0000155 with utils.WorkingDirectory(self._root_dir):
156 ret = self._CloneSources()
asharif56d78bc2013-02-15 21:15:08 +0000157 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000158
asharif0ea89582013-02-15 21:15:11 +0000159 command = 'git checkout %s' % self.branch
asharif56d78bc2013-02-15 21:15:08 +0000160 ret = self._ce.RunCommand(command)
161 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000162
asharif0ea89582013-02-15 21:15:11 +0000163 command = 'git describe --always'
164 ret, o, _ = self._ce.RunCommand(command, return_output=True)
asharif56d78bc2013-02-15 21:15:08 +0000165 self.revision = o.strip()
166 return ret
asharifec9c6242013-02-15 19:56:06 +0000167
asharif56d78bc2013-02-15 21:15:08 +0000168 def SetupForPush(self):
asharif0ea89582013-02-15 21:15:11 +0000169 with utils.WorkingDirectory(self._root_dir):
asharif56d78bc2013-02-15 21:15:08 +0000170 ret = self._CloneSources()
asharif0ea89582013-02-15 21:15:11 +0000171 logger.GetLogger().LogFatalIf(ret, 'Could not clone git repo %s.' %
asharif56d78bc2013-02-15 21:15:08 +0000172 self.address)
asharifec9c6242013-02-15 19:56:06 +0000173
asharif0ea89582013-02-15 21:15:11 +0000174 command = 'git branch -a | grep -wq %s' % self.branch
asharif56d78bc2013-02-15 21:15:08 +0000175 ret = self._ce.RunCommand(command)
asharifec9c6242013-02-15 19:56:06 +0000176
asharif56d78bc2013-02-15 21:15:08 +0000177 if ret == 0:
asharif0ea89582013-02-15 21:15:11 +0000178 if self.branch != 'master':
179 command = ('git branch --track %s remotes/origin/%s' %
180 (self.branch, self.branch))
asharif56d78bc2013-02-15 21:15:08 +0000181 else:
asharif0ea89582013-02-15 21:15:11 +0000182 command = 'pwd'
183 command += '&& git checkout %s' % self.branch
asharif56d78bc2013-02-15 21:15:08 +0000184 else:
asharif0ea89582013-02-15 21:15:11 +0000185 command = 'git symbolic-ref HEAD refs/heads/%s' % self.branch
186 command += '&& rm -rf *'
asharif56d78bc2013-02-15 21:15:08 +0000187 ret = self._ce.RunCommand(command)
188 return ret
189
190 def PushSources(self, commit_message, dry_run=False):
asharif0ea89582013-02-15 21:15:11 +0000191 with utils.WorkingDirectory(self._root_dir):
192 push_args = ''
asharif56d78bc2013-02-15 21:15:08 +0000193 if dry_run:
asharif0ea89582013-02-15 21:15:11 +0000194 push_args += ' -n '
asharif56d78bc2013-02-15 21:15:08 +0000195
asharif0ea89582013-02-15 21:15:11 +0000196 command = 'pwd'
197 for ignore in self.ignores:
198 command += '&& echo \'%s\' >> .git/info/exclude' % ignore
199 command += '&& git add -Av .'
200 command += '&& git commit -v -m \'%s\'' % commit_message
201 command += '; git push -v %s origin %s:%s' % (push_args, self.branch,
202 self.branch)
asharif56d78bc2013-02-15 21:15:08 +0000203 return self._ce.RunCommand(command)
204
205 def MapSources(self, root_dir):
206 if not self.mappings:
207 return
asharif0ea89582013-02-15 21:15:11 +0000208 with utils.WorkingDirectory(self._root_dir):
asharif56d78bc2013-02-15 21:15:08 +0000209 for mapping in self.mappings:
210 remote_path, local_path = SplitMapping(mapping)
asharif0ea89582013-02-15 21:15:11 +0000211 remote_path.rstrip('...')
212 local_path.rstrip('...')
asharif56d78bc2013-02-15 21:15:08 +0000213 ret = self._RsyncExcludingRepoDirs(remote_path,
asharif0ea89582013-02-15 21:15:11 +0000214 os.path.join(root_dir, local_path))
asharif56d78bc2013-02-15 21:15:08 +0000215 if ret: return ret
216 return 0
asharifec9c6242013-02-15 19:56:06 +0000217
218
asharif0ea89582013-02-15 21:15:11 +0000219class RepoReader(object):
asharifec9c6242013-02-15 19:56:06 +0000220 def __init__(self, filename):
221 self.filename = filename
222 self.main_dict = {}
223 self.input_repos = []
224 self.output_repos = []
225
asharifec9c6242013-02-15 19:56:06 +0000226 def ParseFile(self):
asharif0ea89582013-02-15 21:15:11 +0000227 with open(self.filename) as f:
228 self.main_dict = eval(f.read())
229 self.CreateReposFromDict(self.main_dict)
asharifec9c6242013-02-15 19:56:06 +0000230 return [self.input_repos, self.output_repos]
231
asharifec9c6242013-02-15 19:56:06 +0000232 def CreateReposFromDict(self, main_dict):
asharif0ea89582013-02-15 21:15:11 +0000233 for key, repo_list in main_dict.items():
asharifec9c6242013-02-15 19:56:06 +0000234 for repo_dict in repo_list:
235 repo = self.CreateRepoFromDict(repo_dict)
asharif0ea89582013-02-15 21:15:11 +0000236 if key == 'input':
asharifec9c6242013-02-15 19:56:06 +0000237 self.input_repos.append(repo)
asharif0ea89582013-02-15 21:15:11 +0000238 elif key == 'output':
asharifec9c6242013-02-15 19:56:06 +0000239 self.output_repos.append(repo)
240 else:
asharif0ea89582013-02-15 21:15:11 +0000241 logger.GetLogger().LogFatal('Unknown key: %s found' % key)
asharifec9c6242013-02-15 19:56:06 +0000242
asharifec9c6242013-02-15 19:56:06 +0000243 def CreateRepoFromDict(self, repo_dict):
asharif0ea89582013-02-15 21:15:11 +0000244 repo_type = repo_dict.get('type', None)
245 repo_address = repo_dict.get('address', None)
246 repo_mappings = repo_dict.get('mappings', None)
247 repo_ignores = repo_dict.get('ignores', None)
248 repo_branch = repo_dict.get('branch', None)
asharifec9c6242013-02-15 19:56:06 +0000249
asharif0ea89582013-02-15 21:15:11 +0000250 if repo_type == 'p4':
asharifec9c6242013-02-15 19:56:06 +0000251 repo = P4Repo(repo_address,
252 repo_mappings)
asharif0ea89582013-02-15 21:15:11 +0000253 elif repo_type == 'svn':
asharifec9c6242013-02-15 19:56:06 +0000254 repo = SvnRepo(repo_address,
255 repo_mappings)
asharif0ea89582013-02-15 21:15:11 +0000256 elif repo_type == 'git':
asharifec9c6242013-02-15 19:56:06 +0000257 repo = GitRepo(repo_address,
258 repo_branch,
asharif56d78bc2013-02-15 21:15:08 +0000259 mappings=repo_mappings,
asharifec9c6242013-02-15 19:56:06 +0000260 ignores=repo_ignores)
261 else:
asharif0ea89582013-02-15 21:15:11 +0000262 logger.GetLogger().LogFatal('Unknown repo type: %s' % repo_type)
asharifec9c6242013-02-15 19:56:06 +0000263 return repo
264
265
asharif1b92e732013-02-15 20:51:38 +0000266@logger.HandleUncaughtExceptions
asharifec9c6242013-02-15 19:56:06 +0000267def Main(argv):
asharifec9c6242013-02-15 19:56:06 +0000268 parser = optparse.OptionParser()
asharif0ea89582013-02-15 21:15:11 +0000269 parser.add_option('-i',
270 '--input_file',
271 dest='input_file',
272 help='The input file that contains repo descriptions.')
asharifec9c6242013-02-15 19:56:06 +0000273
asharif0ea89582013-02-15 21:15:11 +0000274 parser.add_option('-n',
275 '--dry_run',
276 dest='dry_run',
277 action='store_true',
asharifec9c6242013-02-15 19:56:06 +0000278 default=False,
asharif0ea89582013-02-15 21:15:11 +0000279 help='Do a dry run of the push.')
asharifec9c6242013-02-15 19:56:06 +0000280
281 options = parser.parse_args(argv)[0]
282 if not options.input_file:
283 parser.print_help()
284 return 1
285 rr = RepoReader(options.input_file)
286 [input_repos, output_repos] = rr.ParseFile()
287
288 for output_repo in output_repos:
asharif56d78bc2013-02-15 21:15:08 +0000289 ret = output_repo.SetupForPush()
asharifcdbacaf2013-02-15 20:09:23 +0000290 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000291
292 input_revisions = []
293 for input_repo in input_repos:
asharif56d78bc2013-02-15 21:15:08 +0000294 ret = input_repo.PullSources()
asharifcdbacaf2013-02-15 20:09:23 +0000295 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000296 input_revisions.append(input_repo.revision)
297
asharif56d78bc2013-02-15 21:15:08 +0000298 for input_repo in input_repos:
299 for output_repo in output_repos:
300 ret = input_repo.MapSources(output_repo.GetRoot())
301 if ret: return ret
302
asharif0ea89582013-02-15 21:15:11 +0000303 commit_message = 'Synced repos to: %s' % ','.join(input_revisions)
asharifec9c6242013-02-15 19:56:06 +0000304 for output_repo in output_repos:
asharif56d78bc2013-02-15 21:15:08 +0000305 ret = output_repo.PushSources(commit_message, dry_run=options.dry_run)
asharifcdbacaf2013-02-15 20:09:23 +0000306 if ret: return ret
307
308 if not options.dry_run:
asharif56d78bc2013-02-15 21:15:08 +0000309 for output_repo in output_repos:
310 output_repo.CleanupRoot()
raymes80780842013-02-15 21:59:10 +0000311 for input_repo in input_repos:
312 input_repo.CleanupRoot()
asharifcdbacaf2013-02-15 20:09:23 +0000313
314 return ret
asharifec9c6242013-02-15 19:56:06 +0000315
316
asharif0ea89582013-02-15 21:15:11 +0000317if __name__ == '__main__':
asharifec9c6242013-02-15 19:56:06 +0000318 retval = Main(sys.argv)
319 sys.exit(retval)