blob: 08d61e9a7151bb69c94cb3158e9b62b72061e7c7 [file] [log] [blame]
yunlian994ae3a2013-02-19 21:36:36 +00001#!/usr/bin/python
asharifec9c6242013-02-15 19:56:06 +00002#
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
Han Sheneb77c442013-03-18 13:53:52 -07007import datetime
asharifec9c6242013-02-15 19:56:06 +00008import optparse
9import os
10import re
11import socket
12import sys
13import tempfile
kbaclawski20082a02013-02-16 02:12:57 +000014
asharif0ea89582013-02-15 21:15:11 +000015from automation.clients.helper import perforce
asharifec9c6242013-02-15 19:56:06 +000016from utils import command_executer
17from utils import logger
kbaclawski20082a02013-02-16 02:12:57 +000018from utils import misc
asharifec9c6242013-02-15 19:56:06 +000019
20
asharif1b92e732013-02-15 20:51:38 +000021def GetCanonicalMappings(mappings):
22 canonical_mappings = []
23 for mapping in mappings:
24 remote_path, local_path = mapping.split()
asharif0ea89582013-02-15 21:15:11 +000025 if local_path.endswith('/') and not remote_path.endswith('/'):
asharif1b92e732013-02-15 20:51:38 +000026 local_path = os.path.join(local_path, os.path.basename(remote_path))
27 remote_path = remote_path.lstrip('/').split('/', 1)[1]
28 canonical_mappings.append(perforce.PathMapping(remote_path, local_path))
29 return canonical_mappings
30
asharif0ea89582013-02-15 21:15:11 +000031
asharif56d78bc2013-02-15 21:15:08 +000032def SplitMapping(mapping):
asharif0ea89582013-02-15 21:15:11 +000033 parts = mapping.split()
34 assert len(parts) <= 2, 'Mapping %s invalid' % mapping
35 remote_path = parts[0]
36 if len(parts) == 2:
37 local_path = parts[1]
asharif56d78bc2013-02-15 21:15:08 +000038 else:
asharif0ea89582013-02-15 21:15:11 +000039 local_path = '.'
asharif56d78bc2013-02-15 21:15:08 +000040 return remote_path, local_path
41
asharif1b92e732013-02-15 20:51:38 +000042
43class Repo(object):
Han Sheneb77c442013-03-18 13:53:52 -070044 def __init__(self, no_create_tmp_dir=False):
asharifec9c6242013-02-15 19:56:06 +000045 self.repo_type = None
46 self.address = None
47 self.mappings = None
48 self.revision = None
asharif0ea89582013-02-15 21:15:11 +000049 self.ignores = ['.gitignore', '.p4config', 'README.google']
Han Sheneb77c442013-03-18 13:53:52 -070050 if no_create_tmp_dir:
51 self._root_dir = None
52 else:
53 self._root_dir = tempfile.mkdtemp()
asharif56d78bc2013-02-15 21:15:08 +000054 self._ce = command_executer.GetCommandExecuter()
55 self._logger = logger.GetLogger()
asharifec9c6242013-02-15 19:56:06 +000056
asharif56d78bc2013-02-15 21:15:08 +000057 def PullSources(self):
asharif0ea89582013-02-15 21:15:11 +000058 """Pull all sources into an internal dir."""
asharifec9c6242013-02-15 19:56:06 +000059 pass
60
asharif56d78bc2013-02-15 21:15:08 +000061 def SetupForPush(self):
asharif0ea89582013-02-15 21:15:11 +000062 """Setup a repository for pushing later."""
asharif56d78bc2013-02-15 21:15:08 +000063 pass
64
asharif983c7c42013-02-16 02:13:01 +000065 def PushSources(self, commit_message=None, dry_run=False, message_file=None):
asharif0ea89582013-02-15 21:15:11 +000066 """Push to the external repo with the commit message."""
asharif56d78bc2013-02-15 21:15:08 +000067 pass
68
69 def _RsyncExcludingRepoDirs(self, source_dir, dest_dir):
ashariff6ffe502013-02-15 22:23:12 +000070 for f in os.listdir(source_dir):
71 if f in [".git", ".svn", ".p4config"]:
72 continue
73 dest_file = os.path.join(dest_dir, f)
74 source_file = os.path.join(source_dir, f)
75 if os.path.exists(dest_file):
76 command = "rm -rf %s" % dest_file
77 self._ce.RunCommand(command)
78 command = "rsync -a %s %s" % (source_file, dest_dir)
79 self._ce.RunCommand(command)
80 return 0
asharif56d78bc2013-02-15 21:15:08 +000081
82 def MapSources(self, dest_dir):
83 """Copy sources from the internal dir to root_dir."""
84 return self._RsyncExcludingRepoDirs(self._root_dir, dest_dir)
85
86 def GetRoot(self):
87 return self._root_dir
88
89 def CleanupRoot(self):
asharif0ea89582013-02-15 21:15:11 +000090 command = 'rm -rf %s' % self._root_dir
asharif56d78bc2013-02-15 21:15:08 +000091 return self._ce.RunCommand(command)
asharifec9c6242013-02-15 19:56:06 +000092
93 def __str__(self):
asharif0ea89582013-02-15 21:15:11 +000094 return '\n'.join(str(s) for s in [self.repo_type,
asharif1b92e732013-02-15 20:51:38 +000095 self.address,
96 self.mappings])
asharifec9c6242013-02-15 19:56:06 +000097
98
Han Sheneb77c442013-03-18 13:53:52 -070099# Note - this type of repo is used only for "readonly", in other words, this
100# only serves as a incoming repo.
101class FileRepo(Repo):
102 def __init__(self, address, ignores=None):
103 Repo.__init__(self, no_create_tmp_dir=True)
104 self.repo_type = 'file'
105 self.address = address
106 self.mappings = None
107 self.branch = None
108 self.revision = '{0} (as of "{1}")'.format(address, datetime.datetime.now())
109 self.gerrit = None
110 self._root_dir = self.address
111
112 def CleanupRoot(self):
113 """Override to prevent deletion."""
114 pass
115
116
asharifec9c6242013-02-15 19:56:06 +0000117class P4Repo(Repo):
asharif6578cf82013-02-16 02:41:46 +0000118 def __init__(self, address, mappings, revision=None):
asharifec9c6242013-02-15 19:56:06 +0000119 Repo.__init__(self)
asharif0ea89582013-02-15 21:15:11 +0000120 self.repo_type = 'p4'
asharifec9c6242013-02-15 19:56:06 +0000121 self.address = address
122 self.mappings = mappings
asharif6578cf82013-02-16 02:41:46 +0000123 self.revision = revision
asharifec9c6242013-02-15 19:56:06 +0000124
asharif56d78bc2013-02-15 21:15:08 +0000125 def PullSources(self):
asharifec9c6242013-02-15 19:56:06 +0000126 client_name = socket.gethostname()
asharif0ea89582013-02-15 21:15:11 +0000127 client_name += tempfile.mkstemp()[1].replace('/', '-')
asharifec9c6242013-02-15 19:56:06 +0000128 mappings = self.mappings
asharif0ea89582013-02-15 21:15:11 +0000129 p4view = perforce.View('depot2',
asharif1b92e732013-02-15 20:51:38 +0000130 GetCanonicalMappings(mappings))
asharif0ea89582013-02-15 21:15:11 +0000131 p4client = perforce.CommandsFactory(self._root_dir, p4view,
132 name=client_name)
asharif6578cf82013-02-16 02:41:46 +0000133 command = p4client.SetupAndDo(p4client.Sync(self.revision))
asharif0ea89582013-02-15 21:15:11 +0000134 ret = self._ce.RunCommand(command)
135 assert ret == 0, 'Could not setup client.'
asharif1b92e732013-02-15 20:51:38 +0000136 command = p4client.InCheckoutDir(p4client.SaveCurrentCLNumber())
asharif0ea89582013-02-15 21:15:11 +0000137 ret, o, _ = self._ce.RunCommand(command, return_output=True)
138 assert ret == 0, 'Could not get version from client.'
139 self.revision = re.search('^\d+$', o.strip(), re.MULTILINE).group(0)
asharif1b92e732013-02-15 20:51:38 +0000140 command = p4client.InCheckoutDir(p4client.Remove())
asharif0ea89582013-02-15 21:15:11 +0000141 ret = self._ce.RunCommand(command)
142 assert ret == 0, 'Could not delete client.'
asharif1b92e732013-02-15 20:51:38 +0000143 return 0
asharifec9c6242013-02-15 19:56:06 +0000144
145
146class SvnRepo(Repo):
147 def __init__(self, address, mappings):
148 Repo.__init__(self)
asharif0ea89582013-02-15 21:15:11 +0000149 self.repo_type = 'svn'
asharifec9c6242013-02-15 19:56:06 +0000150 self.address = address
151 self.mappings = mappings
152
asharif56d78bc2013-02-15 21:15:08 +0000153 def PullSources(self):
kbaclawski20082a02013-02-16 02:12:57 +0000154 with misc.WorkingDirectory(self._root_dir):
asharif56d78bc2013-02-15 21:15:08 +0000155 for mapping in self.mappings:
156 remote_path, local_path = SplitMapping(mapping)
asharif0ea89582013-02-15 21:15:11 +0000157 command = 'svn co %s/%s %s' % (self.address, remote_path, local_path)
asharif56d78bc2013-02-15 21:15:08 +0000158 ret = self._ce.RunCommand(command)
asharifcdbacaf2013-02-15 20:09:23 +0000159 if ret: return ret
asharif56d78bc2013-02-15 21:15:08 +0000160
asharif0ea89582013-02-15 21:15:11 +0000161 self.revision = ''
asharif56d78bc2013-02-15 21:15:08 +0000162 for mapping in self.mappings:
163 remote_path, local_path = SplitMapping(mapping)
asharif39f7cb22013-02-19 19:35:16 +0000164 command = 'cd %s && svnversion -c .' % (local_path)
asharif0ea89582013-02-15 21:15:11 +0000165 ret, o, _ = self._ce.RunCommand(command, return_output=True)
asharif39f7cb22013-02-19 19:35:16 +0000166 self.revision += o.strip().split(":")[-1]
asharif56d78bc2013-02-15 21:15:08 +0000167 if ret: return ret
asharifcdbacaf2013-02-15 20:09:23 +0000168 return 0
asharifec9c6242013-02-15 19:56:06 +0000169
170
171class GitRepo(Repo):
asharif2aeeaca2013-02-16 02:11:04 +0000172 def __init__(self, address, branch, mappings=None, ignores=None, gerrit=None):
asharifec9c6242013-02-15 19:56:06 +0000173 Repo.__init__(self)
asharif0ea89582013-02-15 21:15:11 +0000174 self.repo_type = 'git'
asharifec9c6242013-02-15 19:56:06 +0000175 self.address = address
asharif0ea89582013-02-15 21:15:11 +0000176 self.branch = branch or 'master'
asharifec9c6242013-02-15 19:56:06 +0000177 if ignores:
178 self.ignores += ignores
asharif56d78bc2013-02-15 21:15:08 +0000179 self.mappings = mappings
asharif2aeeaca2013-02-16 02:11:04 +0000180 self.gerrit = gerrit
asharifec9c6242013-02-15 19:56:06 +0000181
asharif56d78bc2013-02-15 21:15:08 +0000182 def _CloneSources(self):
kbaclawski20082a02013-02-16 02:12:57 +0000183 with misc.WorkingDirectory(self._root_dir):
asharif0ea89582013-02-15 21:15:11 +0000184 command = 'git clone %s .' % (self.address)
asharif56d78bc2013-02-15 21:15:08 +0000185 return self._ce.RunCommand(command)
asharifec9c6242013-02-15 19:56:06 +0000186
asharif56d78bc2013-02-15 21:15:08 +0000187 def PullSources(self):
kbaclawski20082a02013-02-16 02:12:57 +0000188 with misc.WorkingDirectory(self._root_dir):
asharif0ea89582013-02-15 21:15:11 +0000189 ret = self._CloneSources()
asharif56d78bc2013-02-15 21:15:08 +0000190 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000191
asharif0ea89582013-02-15 21:15:11 +0000192 command = 'git checkout %s' % self.branch
asharif56d78bc2013-02-15 21:15:08 +0000193 ret = self._ce.RunCommand(command)
194 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000195
asharif0ea89582013-02-15 21:15:11 +0000196 command = 'git describe --always'
197 ret, o, _ = self._ce.RunCommand(command, return_output=True)
asharif56d78bc2013-02-15 21:15:08 +0000198 self.revision = o.strip()
199 return ret
asharifec9c6242013-02-15 19:56:06 +0000200
asharif56d78bc2013-02-15 21:15:08 +0000201 def SetupForPush(self):
kbaclawski20082a02013-02-16 02:12:57 +0000202 with misc.WorkingDirectory(self._root_dir):
asharif56d78bc2013-02-15 21:15:08 +0000203 ret = self._CloneSources()
asharif0ea89582013-02-15 21:15:11 +0000204 logger.GetLogger().LogFatalIf(ret, 'Could not clone git repo %s.' %
asharif56d78bc2013-02-15 21:15:08 +0000205 self.address)
asharifec9c6242013-02-15 19:56:06 +0000206
asharif0ea89582013-02-15 21:15:11 +0000207 command = 'git branch -a | grep -wq %s' % self.branch
asharif56d78bc2013-02-15 21:15:08 +0000208 ret = self._ce.RunCommand(command)
asharifec9c6242013-02-15 19:56:06 +0000209
asharif56d78bc2013-02-15 21:15:08 +0000210 if ret == 0:
asharif0ea89582013-02-15 21:15:11 +0000211 if self.branch != 'master':
212 command = ('git branch --track %s remotes/origin/%s' %
213 (self.branch, self.branch))
asharif56d78bc2013-02-15 21:15:08 +0000214 else:
asharif0ea89582013-02-15 21:15:11 +0000215 command = 'pwd'
216 command += '&& git checkout %s' % self.branch
asharif56d78bc2013-02-15 21:15:08 +0000217 else:
asharif0ea89582013-02-15 21:15:11 +0000218 command = 'git symbolic-ref HEAD refs/heads/%s' % self.branch
219 command += '&& rm -rf *'
asharif56d78bc2013-02-15 21:15:08 +0000220 ret = self._ce.RunCommand(command)
221 return ret
222
Han Sheneb77c442013-03-18 13:53:52 -0700223 def CommitLocally(self, commit_message=None, message_file=None):
kbaclawski20082a02013-02-16 02:12:57 +0000224 with misc.WorkingDirectory(self._root_dir):
asharif0ea89582013-02-15 21:15:11 +0000225 command = 'pwd'
226 for ignore in self.ignores:
227 command += '&& echo \'%s\' >> .git/info/exclude' % ignore
228 command += '&& git add -Av .'
asharif983c7c42013-02-16 02:13:01 +0000229 if message_file:
230 message_arg = '-F %s' % message_file
231 elif commit_message:
232 message_arg = '-m \'%s\'' % commit_message
233 else:
234 raise Exception("No commit message given!")
235 command += '&& git commit -v %s' % message_arg
Han Sheneb77c442013-03-18 13:53:52 -0700236 return self._ce.RunCommand(command)
237
238 def PushSources(self, commit_message=None, dry_run=False, message_file=None):
239 ret = self.CommitLocally(commit_message, message_file)
240 if ret: return ret
241 push_args = ''
242 if dry_run:
243 push_args += ' -n '
244 with misc.WorkingDirectory(self._root_dir):
asharif2aeeaca2013-02-16 02:11:04 +0000245 if self.gerrit:
246 label = 'somelabel'
247 command = 'git remote add %s %s' % (label, self.address)
248 command += ('&& git push %s %s HEAD:refs/for/master' %
249 (push_args,label))
250 else:
251 command = 'git push -v %s origin %s:%s' % (push_args, self.branch,
252 self.branch)
253 ret = self._ce.RunCommand(command)
Han Sheneb77c442013-03-18 13:53:52 -0700254 return ret
asharif56d78bc2013-02-15 21:15:08 +0000255
256 def MapSources(self, root_dir):
257 if not self.mappings:
ashariff6ffe502013-02-15 22:23:12 +0000258 self._RsyncExcludingRepoDirs(self._root_dir, root_dir)
asharif56d78bc2013-02-15 21:15:08 +0000259 return
kbaclawski20082a02013-02-16 02:12:57 +0000260 with misc.WorkingDirectory(self._root_dir):
asharif56d78bc2013-02-15 21:15:08 +0000261 for mapping in self.mappings:
262 remote_path, local_path = SplitMapping(mapping)
asharif0ea89582013-02-15 21:15:11 +0000263 remote_path.rstrip('...')
264 local_path.rstrip('...')
ashariff6ffe502013-02-15 22:23:12 +0000265 full_local_path = os.path.join(root_dir, local_path)
266 ret = self._RsyncExcludingRepoDirs(remote_path, full_local_path)
asharif56d78bc2013-02-15 21:15:08 +0000267 if ret: return ret
268 return 0
asharifec9c6242013-02-15 19:56:06 +0000269
270
asharif0ea89582013-02-15 21:15:11 +0000271class RepoReader(object):
asharifec9c6242013-02-15 19:56:06 +0000272 def __init__(self, filename):
273 self.filename = filename
274 self.main_dict = {}
275 self.input_repos = []
276 self.output_repos = []
277
asharifec9c6242013-02-15 19:56:06 +0000278 def ParseFile(self):
asharif0ea89582013-02-15 21:15:11 +0000279 with open(self.filename) as f:
280 self.main_dict = eval(f.read())
281 self.CreateReposFromDict(self.main_dict)
asharifec9c6242013-02-15 19:56:06 +0000282 return [self.input_repos, self.output_repos]
283
asharifec9c6242013-02-15 19:56:06 +0000284 def CreateReposFromDict(self, main_dict):
asharif0ea89582013-02-15 21:15:11 +0000285 for key, repo_list in main_dict.items():
asharifec9c6242013-02-15 19:56:06 +0000286 for repo_dict in repo_list:
287 repo = self.CreateRepoFromDict(repo_dict)
asharif0ea89582013-02-15 21:15:11 +0000288 if key == 'input':
asharifec9c6242013-02-15 19:56:06 +0000289 self.input_repos.append(repo)
asharif0ea89582013-02-15 21:15:11 +0000290 elif key == 'output':
asharifec9c6242013-02-15 19:56:06 +0000291 self.output_repos.append(repo)
292 else:
asharif0ea89582013-02-15 21:15:11 +0000293 logger.GetLogger().LogFatal('Unknown key: %s found' % key)
asharifec9c6242013-02-15 19:56:06 +0000294
asharifec9c6242013-02-15 19:56:06 +0000295 def CreateRepoFromDict(self, repo_dict):
asharif0ea89582013-02-15 21:15:11 +0000296 repo_type = repo_dict.get('type', None)
297 repo_address = repo_dict.get('address', None)
298 repo_mappings = repo_dict.get('mappings', None)
299 repo_ignores = repo_dict.get('ignores', None)
300 repo_branch = repo_dict.get('branch', None)
asharif2aeeaca2013-02-16 02:11:04 +0000301 gerrit = repo_dict.get('gerrit', None)
asharif6578cf82013-02-16 02:41:46 +0000302 revision = repo_dict.get('revision', None)
asharifec9c6242013-02-15 19:56:06 +0000303
asharif0ea89582013-02-15 21:15:11 +0000304 if repo_type == 'p4':
asharifec9c6242013-02-15 19:56:06 +0000305 repo = P4Repo(repo_address,
asharif6578cf82013-02-16 02:41:46 +0000306 repo_mappings,
307 revision=revision)
asharif0ea89582013-02-15 21:15:11 +0000308 elif repo_type == 'svn':
asharifec9c6242013-02-15 19:56:06 +0000309 repo = SvnRepo(repo_address,
310 repo_mappings)
asharif0ea89582013-02-15 21:15:11 +0000311 elif repo_type == 'git':
asharifec9c6242013-02-15 19:56:06 +0000312 repo = GitRepo(repo_address,
313 repo_branch,
asharif56d78bc2013-02-15 21:15:08 +0000314 mappings=repo_mappings,
asharif2aeeaca2013-02-16 02:11:04 +0000315 ignores=repo_ignores,
316 gerrit=gerrit)
Han Sheneb77c442013-03-18 13:53:52 -0700317 elif repo_type == 'file':
318 repo = FileRepo(repo_address)
asharifec9c6242013-02-15 19:56:06 +0000319 else:
asharif0ea89582013-02-15 21:15:11 +0000320 logger.GetLogger().LogFatal('Unknown repo type: %s' % repo_type)
asharifec9c6242013-02-15 19:56:06 +0000321 return repo
322
323
asharif1b92e732013-02-15 20:51:38 +0000324@logger.HandleUncaughtExceptions
asharifec9c6242013-02-15 19:56:06 +0000325def Main(argv):
asharifec9c6242013-02-15 19:56:06 +0000326 parser = optparse.OptionParser()
asharif0ea89582013-02-15 21:15:11 +0000327 parser.add_option('-i',
328 '--input_file',
329 dest='input_file',
330 help='The input file that contains repo descriptions.')
asharifec9c6242013-02-15 19:56:06 +0000331
asharif0ea89582013-02-15 21:15:11 +0000332 parser.add_option('-n',
333 '--dry_run',
334 dest='dry_run',
335 action='store_true',
asharifec9c6242013-02-15 19:56:06 +0000336 default=False,
asharif0ea89582013-02-15 21:15:11 +0000337 help='Do a dry run of the push.')
asharifec9c6242013-02-15 19:56:06 +0000338
asharif983c7c42013-02-16 02:13:01 +0000339 parser.add_option('-F',
340 '--message_file',
341 dest='message_file',
342 default=None,
343 help='Use contents of the log file as the commit message.')
344
asharifec9c6242013-02-15 19:56:06 +0000345 options = parser.parse_args(argv)[0]
346 if not options.input_file:
347 parser.print_help()
348 return 1
349 rr = RepoReader(options.input_file)
350 [input_repos, output_repos] = rr.ParseFile()
351
Han Sheneb77c442013-03-18 13:53:52 -0700352 # Make sure FileRepo is not used as output destination.
353 for output_repo in output_repos:
354 if output_repo.repo_type == 'file':
355 logger.GetLogger().LogFatal(
356 'FileRepo is only supported as an input repo.')
357
asharifec9c6242013-02-15 19:56:06 +0000358 for output_repo in output_repos:
asharif56d78bc2013-02-15 21:15:08 +0000359 ret = output_repo.SetupForPush()
asharifcdbacaf2013-02-15 20:09:23 +0000360 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000361
362 input_revisions = []
363 for input_repo in input_repos:
asharif56d78bc2013-02-15 21:15:08 +0000364 ret = input_repo.PullSources()
asharifcdbacaf2013-02-15 20:09:23 +0000365 if ret: return ret
asharifec9c6242013-02-15 19:56:06 +0000366 input_revisions.append(input_repo.revision)
367
asharif56d78bc2013-02-15 21:15:08 +0000368 for input_repo in input_repos:
369 for output_repo in output_repos:
370 ret = input_repo.MapSources(output_repo.GetRoot())
371 if ret: return ret
372
asharif0ea89582013-02-15 21:15:11 +0000373 commit_message = 'Synced repos to: %s' % ','.join(input_revisions)
asharifec9c6242013-02-15 19:56:06 +0000374 for output_repo in output_repos:
asharif983c7c42013-02-16 02:13:01 +0000375 ret = output_repo.PushSources(commit_message=commit_message,
376 dry_run=options.dry_run,
377 message_file=options.message_file)
asharifcdbacaf2013-02-15 20:09:23 +0000378 if ret: return ret
379
380 if not options.dry_run:
asharif56d78bc2013-02-15 21:15:08 +0000381 for output_repo in output_repos:
382 output_repo.CleanupRoot()
raymes80780842013-02-15 21:59:10 +0000383 for input_repo in input_repos:
384 input_repo.CleanupRoot()
asharifcdbacaf2013-02-15 20:09:23 +0000385
386 return ret
asharifec9c6242013-02-15 19:56:06 +0000387
388
asharif0ea89582013-02-15 21:15:11 +0000389if __name__ == '__main__':
asharifec9c6242013-02-15 19:56:06 +0000390 retval = Main(sys.argv)
391 sys.exit(retval)