blob: e3c3bd51735e58323bf63bde12c971c4800d9bb8 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
43if not is_python3():
44 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090046 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053047
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070048
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070049def _lwrite(path, content):
50 lock = '%s.lock' % path
51
Chirayu Desai303a82f2014-08-19 22:57:17 +053052 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070053 try:
54 fd.write(content)
55 finally:
56 fd.close()
57
58 try:
59 os.rename(lock, path)
60 except OSError:
61 os.remove(lock)
62 raise
63
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070064
Shawn O. Pearce48244782009-04-16 08:25:57 -070065def _error(fmt, *args):
66 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070067 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070068
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070069
David Pursehousef33929d2015-08-24 14:39:14 +090070def _warn(fmt, *args):
71 msg = fmt % args
72 print('warn: %s' % msg, file=sys.stderr)
73
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070074
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070075def not_rev(r):
76 return '^' + r
77
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070078
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080079def sq(r):
80 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
Jonathan Nieder93719792015-03-17 11:29:58 -070082_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
84
Jonathan Nieder93719792015-03-17 11:29:58 -070085def _ProjectHooks():
86 """List the hooks present in the 'hooks' directory.
87
88 These hooks are project hooks and are copied to the '.git/hooks' directory
89 of all subprojects.
90
91 This function caches the list of hooks (based on the contents of the
92 'repo/hooks' directory) on the first call.
93
94 Returns:
95 A list of absolute paths to all of the files in the hooks directory.
96 """
97 global _project_hook_list
98 if _project_hook_list is None:
99 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
100 d = os.path.join(d, 'hooks')
101 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
102 return _project_hook_list
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
119 '--abbrev-commit',
120 '--pretty=oneline',
121 '--reverse',
122 '--date-order',
123 not_rev(self.base),
124 self.commit,
125 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700126 return self._commit_cache
127
128
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129class ReviewableBranch(object):
130 _commit_cache = None
131
132 def __init__(self, project, branch, base):
133 self.project = project
134 self.branch = branch
135 self.base = base
136
137 @property
138 def name(self):
139 return self.branch.name
140
141 @property
142 def commits(self):
143 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700144 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
145 '--abbrev-commit',
146 '--pretty=oneline',
147 '--reverse',
148 '--date-order',
149 not_rev(self.base),
150 R_HEADS + self.name,
151 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700152 return self._commit_cache
153
154 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800155 def unabbrev_commits(self):
156 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700157 for commit in self.project.bare_git.rev_list(not_rev(self.base),
158 R_HEADS + self.name,
159 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800160 r[commit[0:8]] = commit
161 return r
162
163 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700165 return self.project.bare_git.log('--pretty=format:%cd',
166 '-n', '1',
167 R_HEADS + self.name,
168 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 def UploadForReview(self, people,
171 auto_topic=False,
172 draft=False,
173 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800174 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700175 people,
Brian Harring435370c2012-07-28 15:37:04 -0700176 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400177 draft=draft,
178 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700180 def GetPublishedRefs(self):
181 refs = {}
182 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700183 self.branch.remote.SshReviewUrl(self.project.UserEmail),
184 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700185 for line in output.split('\n'):
186 try:
187 (sha, ref) = line.split()
188 refs[sha] = ref
189 except ValueError:
190 pass
191
192 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197 def __init__(self, config):
198 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100199 self.project = self.printer('header', attr='bold')
200 self.branch = self.printer('header', attr='bold')
201 self.nobranch = self.printer('nobranch', fg='red')
202 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
Anthony King7bdac712014-07-16 12:56:40 +0100204 self.added = self.printer('added', fg='green')
205 self.changed = self.printer('changed', fg='red')
206 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207
208
209class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700210
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 def __init__(self, config):
212 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100213 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700215
Anthony King7bdac712014-07-16 12:56:40 +0100216class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700217
James W. Mills24c13082012-04-12 15:04:13 -0500218 def __init__(self, name, value, keep):
219 self.name = name
220 self.value = value
221 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
Anthony King7bdac712014-07-16 12:56:40 +0100224class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700225
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800226 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 self.src = src
228 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800229 self.abs_src = abssrc
230 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
232 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800233 src = self.abs_src
234 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 # copy file if it does not exist or is out of date
236 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
237 try:
238 # remove existing file first, since it might be read-only
239 if os.path.exists(dest):
240 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400241 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200242 dest_dir = os.path.dirname(dest)
243 if not os.path.isdir(dest_dir):
244 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 shutil.copy(src, dest)
246 # make the file read-only
247 mode = os.stat(dest)[stat.ST_MODE]
248 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
249 os.chmod(dest, mode)
250 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700251 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
Anthony King7bdac712014-07-16 12:56:40 +0100254class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
Wink Saville4c426ef2015-06-03 08:05:17 -0700256 def __init__(self, git_worktree, src, dest, relsrc, absdest):
257 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500258 self.src = src
259 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700260 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500261 self.abs_dest = absdest
262
Wink Saville4c426ef2015-06-03 08:05:17 -0700263 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500264 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700265 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500266 try:
267 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800268 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500270 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700271 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 if not os.path.isdir(dest_dir):
273 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700274 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500275 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 _error('Cannot link file %s to %s', relSrc, absDest)
277
278 def _Link(self):
279 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
280 on the src linking all of the files in the source in to the destination
281 directory.
282 """
283 # We use the absSrc to handle the situation where the current directory
284 # is not the root of the repo
285 absSrc = os.path.join(self.git_worktree, self.src)
286 if os.path.exists(absSrc):
287 # Entity exists so just a simple one to one link operation
288 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
289 else:
290 # Entity doesn't exist assume there is a wild card
291 absDestDir = self.abs_dest
292 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
293 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700294 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700295 else:
296 absSrcFiles = glob.glob(absSrc)
297 for absSrcFile in absSrcFiles:
298 # Create a releative path from source dir to destination dir
299 absSrcDir = os.path.dirname(absSrcFile)
300 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
301
302 # Get the source file name
303 srcFile = os.path.basename(absSrcFile)
304
305 # Now form the final full paths to srcFile. They will be
306 # absolute for the desintaiton and relative for the srouce.
307 absDest = os.path.join(absDestDir, srcFile)
308 relSrc = os.path.join(relSrcDir, srcFile)
309 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500310
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700311
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700312class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700313
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700314 def __init__(self,
315 name,
Anthony King7bdac712014-07-16 12:56:40 +0100316 url=None,
317 review=None,
318 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700319 self.name = name
320 self.url = url
321 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100322 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700323
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Doug Anderson37282b42011-03-04 11:54:18 -0800325class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700326
Doug Anderson37282b42011-03-04 11:54:18 -0800327 """A RepoHook contains information about a script to run as a hook.
328
329 Hooks are used to run a python script before running an upload (for instance,
330 to run presubmit checks). Eventually, we may have hooks for other actions.
331
332 This shouldn't be confused with files in the 'repo/hooks' directory. Those
333 files are copied into each '.git/hooks' folder for each project. Repo-level
334 hooks are associated instead with repo actions.
335
336 Hooks are always python. When a hook is run, we will load the hook into the
337 interpreter and execute its main() function.
338 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700339
Doug Anderson37282b42011-03-04 11:54:18 -0800340 def __init__(self,
341 hook_type,
342 hooks_project,
343 topdir,
344 abort_if_user_denies=False):
345 """RepoHook constructor.
346
347 Params:
348 hook_type: A string representing the type of hook. This is also used
349 to figure out the name of the file containing the hook. For
350 example: 'pre-upload'.
351 hooks_project: The project containing the repo hooks. If you have a
352 manifest, this is manifest.repo_hooks_project. OK if this is None,
353 which will make the hook a no-op.
354 topdir: Repo's top directory (the one containing the .repo directory).
355 Scripts will run with CWD as this directory. If you have a manifest,
356 this is manifest.topdir
357 abort_if_user_denies: If True, we'll throw a HookError() if the user
358 doesn't allow us to run the hook.
359 """
360 self._hook_type = hook_type
361 self._hooks_project = hooks_project
362 self._topdir = topdir
363 self._abort_if_user_denies = abort_if_user_denies
364
365 # Store the full path to the script for convenience.
366 if self._hooks_project:
367 self._script_fullpath = os.path.join(self._hooks_project.worktree,
368 self._hook_type + '.py')
369 else:
370 self._script_fullpath = None
371
372 def _GetHash(self):
373 """Return a hash of the contents of the hooks directory.
374
375 We'll just use git to do this. This hash has the property that if anything
376 changes in the directory we will return a different has.
377
378 SECURITY CONSIDERATION:
379 This hash only represents the contents of files in the hook directory, not
380 any other files imported or called by hooks. Changes to imported files
381 can change the script behavior without affecting the hash.
382
383 Returns:
384 A string representing the hash. This will always be ASCII so that it can
385 be printed to the user easily.
386 """
387 assert self._hooks_project, "Must have hooks to calculate their hash."
388
389 # We will use the work_git object rather than just calling GetRevisionId().
390 # That gives us a hash of the latest checked in version of the files that
391 # the user will actually be executing. Specifically, GetRevisionId()
392 # doesn't appear to change even if a user checks out a different version
393 # of the hooks repo (via git checkout) nor if a user commits their own revs.
394 #
395 # NOTE: Local (non-committed) changes will not be factored into this hash.
396 # I think this is OK, since we're really only worried about warning the user
397 # about upstream changes.
398 return self._hooks_project.work_git.rev_parse('HEAD')
399
400 def _GetMustVerb(self):
401 """Return 'must' if the hook is required; 'should' if not."""
402 if self._abort_if_user_denies:
403 return 'must'
404 else:
405 return 'should'
406
407 def _CheckForHookApproval(self):
408 """Check to see whether this hook has been approved.
409
410 We'll look at the hash of all of the hooks. If this matches the hash that
411 the user last approved, we're done. If it doesn't, we'll ask the user
412 about approval.
413
414 Note that we ask permission for each individual hook even though we use
415 the hash of all hooks when detecting changes. We'd like the user to be
416 able to approve / deny each hook individually. We only use the hash of all
417 hooks because there is no other easy way to detect changes to local imports.
418
419 Returns:
420 True if this hook is approved to run; False otherwise.
421
422 Raises:
423 HookError: Raised if the user doesn't approve and abort_if_user_denies
424 was passed to the consturctor.
425 """
Doug Anderson37282b42011-03-04 11:54:18 -0800426 hooks_config = self._hooks_project.config
427 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
428
429 # Get the last hash that the user approved for this hook; may be None.
430 old_hash = hooks_config.GetString(git_approval_key)
431
432 # Get the current hash so we can tell if scripts changed since approval.
433 new_hash = self._GetHash()
434
435 if old_hash is not None:
436 # User previously approved hook and asked not to be prompted again.
437 if new_hash == old_hash:
438 # Approval matched. We're done.
439 return True
440 else:
441 # Give the user a reason why we're prompting, since they last told
442 # us to "never ask again".
443 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
444 self._hook_type)
445 else:
446 prompt = ''
447
448 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
449 if sys.stdout.isatty():
450 prompt += ('Repo %s run the script:\n'
451 ' %s\n'
452 '\n'
453 'Do you want to allow this script to run '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700454 '(yes/yes-never-ask-again/NO)? ') % (self._GetMustVerb(),
455 self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530456 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900457 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800458
459 # User is doing a one-time approval.
460 if response in ('y', 'yes'):
461 return True
462 elif response == 'yes-never-ask-again':
463 hooks_config.SetString(git_approval_key, new_hash)
464 return True
465
466 # For anything else, we'll assume no approval.
467 if self._abort_if_user_denies:
468 raise HookError('You must allow the %s hook or use --no-verify.' %
469 self._hook_type)
470
471 return False
472
473 def _ExecuteHook(self, **kwargs):
474 """Actually execute the given hook.
475
476 This will run the hook's 'main' function in our python interpreter.
477
478 Args:
479 kwargs: Keyword arguments to pass to the hook. These are often specific
480 to the hook type. For instance, pre-upload hooks will contain
481 a project_list.
482 """
483 # Keep sys.path and CWD stashed away so that we can always restore them
484 # upon function exit.
485 orig_path = os.getcwd()
486 orig_syspath = sys.path
487
488 try:
489 # Always run hooks with CWD as topdir.
490 os.chdir(self._topdir)
491
492 # Put the hook dir as the first item of sys.path so hooks can do
493 # relative imports. We want to replace the repo dir as [0] so
494 # hooks can't import repo files.
495 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
496
497 # Exec, storing global context in the context dict. We catch exceptions
498 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500499 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800500 try:
Anthony King70f68902014-05-05 21:15:34 +0100501 exec(compile(open(self._script_fullpath).read(),
502 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800503 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700504 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
505 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800506
507 # Running the script should have defined a main() function.
508 if 'main' not in context:
509 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
510
Doug Anderson37282b42011-03-04 11:54:18 -0800511 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
512 # We don't actually want hooks to define their main with this argument--
513 # it's there to remind them that their hook should always take **kwargs.
514 # For instance, a pre-upload hook should be defined like:
515 # def main(project_list, **kwargs):
516 #
517 # This allows us to later expand the API without breaking old hooks.
518 kwargs = kwargs.copy()
519 kwargs['hook_should_take_kwargs'] = True
520
521 # Call the main function in the hook. If the hook should cause the
522 # build to fail, it will raise an Exception. We'll catch that convert
523 # to a HookError w/ just the failing traceback.
524 try:
525 context['main'](**kwargs)
526 except Exception:
527 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700528 'above.' % (traceback.format_exc(),
529 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800530 finally:
531 # Restore sys.path and CWD.
532 sys.path = orig_syspath
533 os.chdir(orig_path)
534
535 def Run(self, user_allows_all_hooks, **kwargs):
536 """Run the hook.
537
538 If the hook doesn't exist (because there is no hooks project or because
539 this particular hook is not enabled), this is a no-op.
540
541 Args:
542 user_allows_all_hooks: If True, we will never prompt about running the
543 hook--we'll just assume it's OK to run it.
544 kwargs: Keyword arguments to pass to the hook. These are often specific
545 to the hook type. For instance, pre-upload hooks will contain
546 a project_list.
547
548 Raises:
549 HookError: If there was a problem finding the hook or the user declined
550 to run a required hook (from _CheckForHookApproval).
551 """
552 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700553 if ((not self._hooks_project) or (self._hook_type not in
554 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800555 return
556
557 # Bail with a nice error if we can't find the hook.
558 if not os.path.isfile(self._script_fullpath):
559 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
560
561 # Make sure the user is OK with running the hook.
562 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
563 return
564
565 # Run the hook with the same version of python we're using.
566 self._ExecuteHook(**kwargs)
567
568
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700569class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600570 # These objects can be shared between several working trees.
571 shareable_files = ['description', 'info']
572 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
573 # These objects can only be used by a single working tree.
574 working_tree_files = ['config', 'packed-refs', 'shallow']
575 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700577 def __init__(self,
578 manifest,
579 name,
580 remote,
581 gitdir,
David James8d201162013-10-11 17:03:19 -0700582 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 worktree,
584 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700585 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800586 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100587 rebase=True,
588 groups=None,
589 sync_c=False,
590 sync_s=False,
591 clone_depth=None,
592 upstream=None,
593 parent=None,
594 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900595 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700596 optimized_fetch=False,
597 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800598 """Init a Project object.
599
600 Args:
601 manifest: The XmlManifest object.
602 name: The `name` attribute of manifest.xml's project element.
603 remote: RemoteSpec object specifying its remote's properties.
604 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700605 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800606 worktree: Absolute path of git working tree.
607 relpath: Relative path of git working tree to repo's top directory.
608 revisionExpr: The `revision` attribute of manifest.xml's project element.
609 revisionId: git commit id for checking out.
610 rebase: The `rebase` attribute of manifest.xml's project element.
611 groups: The `groups` attribute of manifest.xml's project element.
612 sync_c: The `sync-c` attribute of manifest.xml's project element.
613 sync_s: The `sync-s` attribute of manifest.xml's project element.
614 upstream: The `upstream` attribute of manifest.xml's project element.
615 parent: The parent Project object.
616 is_derived: False if the project was explicitly defined in the manifest;
617 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400618 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900619 optimized_fetch: If True, when a project is set to a sha1 revision, only
620 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700621 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800622 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700623 self.manifest = manifest
624 self.name = name
625 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800626 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700627 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800628 if worktree:
629 self.worktree = worktree.replace('\\', '/')
630 else:
631 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700633 self.revisionExpr = revisionExpr
634
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700635 if revisionId is None \
636 and revisionExpr \
637 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700638 self.revisionId = revisionExpr
639 else:
640 self.revisionId = revisionId
641
Mike Pontillod3153822012-02-28 11:53:24 -0800642 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700643 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700644 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800645 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900646 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700647 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800648 self.parent = parent
649 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900650 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800651 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800652
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500655 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500656 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700657 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
658 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800660 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700661 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800662 else:
663 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700664 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700665 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700666 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400667 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700668 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
Doug Anderson37282b42011-03-04 11:54:18 -0800670 # This will be filled in if a project is later identified to be the
671 # project containing repo hooks.
672 self.enabled_repo_hooks = []
673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800675 def Derived(self):
676 return self.is_derived
677
678 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600680 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681
682 @property
683 def CurrentBranch(self):
684 """Obtain the name of the currently checked out branch.
685 The branch name omits the 'refs/heads/' prefix.
686 None is returned if the project is on a detached HEAD.
687 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700688 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 if b.startswith(R_HEADS):
690 return b[len(R_HEADS):]
691 return None
692
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700693 def IsRebaseInProgress(self):
694 w = self.worktree
695 g = os.path.join(w, '.git')
696 return os.path.exists(os.path.join(g, 'rebase-apply')) \
697 or os.path.exists(os.path.join(g, 'rebase-merge')) \
698 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 def IsDirty(self, consider_untracked=True):
701 """Is the working directory modified in some way?
702 """
703 self.work_git.update_index('-q',
704 '--unmerged',
705 '--ignore-missing',
706 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900707 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 return True
709 if self.work_git.DiffZ('diff-files'):
710 return True
711 if consider_untracked and self.work_git.LsOthers():
712 return True
713 return False
714
715 _userident_name = None
716 _userident_email = None
717
718 @property
719 def UserName(self):
720 """Obtain the user's personal name.
721 """
722 if self._userident_name is None:
723 self._LoadUserIdentity()
724 return self._userident_name
725
726 @property
727 def UserEmail(self):
728 """Obtain the user's email address. This is very likely
729 to be their Gerrit login.
730 """
731 if self._userident_email is None:
732 self._LoadUserIdentity()
733 return self._userident_email
734
735 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900736 u = self.bare_git.var('GIT_COMMITTER_IDENT')
737 m = re.compile("^(.*) <([^>]*)> ").match(u)
738 if m:
739 self._userident_name = m.group(1)
740 self._userident_email = m.group(2)
741 else:
742 self._userident_name = ''
743 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744
745 def GetRemote(self, name):
746 """Get the configuration for a single remote.
747 """
748 return self.config.GetRemote(name)
749
750 def GetBranch(self, name):
751 """Get the configuration for a single branch.
752 """
753 return self.config.GetBranch(name)
754
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700755 def GetBranches(self):
756 """Get all existing local branches.
757 """
758 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900759 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700760 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700761
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530762 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700763 if name.startswith(R_HEADS):
764 name = name[len(R_HEADS):]
765 b = self.GetBranch(name)
766 b.current = name == current
767 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900768 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700769 heads[name] = b
770
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530771 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700772 if name.startswith(R_PUB):
773 name = name[len(R_PUB):]
774 b = heads.get(name)
775 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900776 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700777
778 return heads
779
Colin Cross5acde752012-03-28 20:15:45 -0700780 def MatchesGroups(self, manifest_groups):
781 """Returns true if the manifest groups specified at init should cause
782 this project to be synced.
783 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700784 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700785
786 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700787 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700788 manifest_groups: "-group1,group2"
789 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500790
791 The special manifest group "default" will match any project that
792 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700793 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500794 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700795 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700796 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500797 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700798
Conley Owens971de8e2012-04-16 10:36:08 -0700799 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700800 for group in expanded_manifest_groups:
801 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700802 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700803 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700804 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700805
Conley Owens971de8e2012-04-16 10:36:08 -0700806 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700808# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700809 def UncommitedFiles(self, get_all=True):
810 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700812 Args:
813 get_all: a boolean, if True - get information about all different
814 uncommitted files. If False - return as soon as any kind of
815 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500816 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700817 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500818 self.work_git.update_index('-q',
819 '--unmerged',
820 '--ignore-missing',
821 '--refresh')
822 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700823 details.append("rebase in progress")
824 if not get_all:
825 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500826
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700827 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
828 if changes:
829 details.extend(changes)
830 if not get_all:
831 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500832
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700833 changes = self.work_git.DiffZ('diff-files').keys()
834 if changes:
835 details.extend(changes)
836 if not get_all:
837 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500838
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700839 changes = self.work_git.LsOthers()
840 if changes:
841 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500842
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700843 return details
844
845 def HasChanges(self):
846 """Returns true if there are uncommitted changes.
847 """
848 if self.UncommitedFiles(get_all=False):
849 return True
850 else:
851 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500852
Terence Haddock4655e812011-03-31 12:33:34 +0200853 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200855
856 Args:
857 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 """
859 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700860 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200861 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700862 print(file=output_redir)
863 print('project %s/' % self.relpath, file=output_redir)
864 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 return
866
867 self.work_git.update_index('-q',
868 '--unmerged',
869 '--ignore-missing',
870 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700871 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
873 df = self.work_git.DiffZ('diff-files')
874 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100875 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700876 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
878 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700879 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200880 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700881 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882
883 branch = self.CurrentBranch
884 if branch is None:
885 out.nobranch('(*** NO BRANCH ***)')
886 else:
887 out.branch('branch %s', branch)
888 out.nl()
889
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700890 if rb:
891 out.important('prior sync failed; rebase still in progress')
892 out.nl()
893
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 paths = list()
895 paths.extend(di.keys())
896 paths.extend(df.keys())
897 paths.extend(do)
898
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530899 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900900 try:
901 i = di[p]
902 except KeyError:
903 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900905 try:
906 f = df[p]
907 except KeyError:
908 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200909
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900910 if i:
911 i_status = i.status.upper()
912 else:
913 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900915 if f:
916 f_status = f.status.lower()
917 else:
918 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919
920 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800921 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700922 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 else:
924 line = ' %s%s\t%s' % (i_status, f_status, p)
925
926 if i and not f:
927 out.added('%s', line)
928 elif (i and f) or (not i and f):
929 out.changed('%s', line)
930 elif not i and not f:
931 out.untracked('%s', line)
932 else:
933 out.write('%s', line)
934 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200935
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700936 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937
pelyad67872d2012-03-28 14:49:58 +0300938 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 """Prints the status of the repository to stdout.
940 """
941 out = DiffColoring(self.config)
942 cmd = ['diff']
943 if out.is_on:
944 cmd.append('--color')
945 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300946 if absolute_paths:
947 cmd.append('--src-prefix=a/%s/' % self.relpath)
948 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 cmd.append('--')
950 p = GitCommand(self,
951 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100952 capture_stdout=True,
953 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 has_diff = False
955 for line in p.process.stdout:
956 if not has_diff:
957 out.nl()
958 out.project('project %s/' % self.relpath)
959 out.nl()
960 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700961 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 p.Wait()
963
964
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700965# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 """Was the branch published (uploaded) for code review?
969 If so, returns the SHA-1 hash of the last published
970 state for the branch.
971 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700972 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900973 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700974 try:
975 return self.bare_git.rev_parse(key)
976 except GitError:
977 return None
978 else:
979 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900980 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700981 except KeyError:
982 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983
David Pursehouse8a68ff92012-09-24 12:15:13 +0900984 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 """Prunes any stale published refs.
986 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900987 if all_refs is None:
988 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 heads = set()
990 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530991 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 if name.startswith(R_HEADS):
993 heads.add(name)
994 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900995 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530997 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 n = name[len(R_PUB):]
999 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001000 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001002 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 """List any branches which can be uploaded for review.
1004 """
1005 heads = {}
1006 pubed = {}
1007
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301008 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001010 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001012 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013
1014 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301015 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001016 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001018 if selected_branch and branch != selected_branch:
1019 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001021 rb = self.GetUploadableBranch(branch)
1022 if rb:
1023 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 return ready
1025
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001026 def GetUploadableBranch(self, branch_name):
1027 """Get a single uploadable branch, or None.
1028 """
1029 branch = self.GetBranch(branch_name)
1030 base = branch.LocalMerge
1031 if branch.LocalMerge:
1032 rb = ReviewableBranch(self, branch, base)
1033 if rb.commits:
1034 return rb
1035 return None
1036
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001037 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001038 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001039 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001040 draft=False,
1041 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 """Uploads the named branch for code review.
1043 """
1044 if branch is None:
1045 branch = self.CurrentBranch
1046 if branch is None:
1047 raise GitError('not currently on a branch')
1048
1049 branch = self.GetBranch(branch)
1050 if not branch.LocalMerge:
1051 raise GitError('branch %s does not track a remote' % branch.name)
1052 if not branch.remote.review:
1053 raise GitError('remote %s has no review url' % branch.remote.name)
1054
Bryan Jacobsf609f912013-05-06 13:36:24 -04001055 if dest_branch is None:
1056 dest_branch = self.dest_branch
1057 if dest_branch is None:
1058 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 if not dest_branch.startswith(R_HEADS):
1060 dest_branch = R_HEADS + dest_branch
1061
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001062 if not branch.remote.projectname:
1063 branch.remote.projectname = self.name
1064 branch.remote.Save()
1065
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001066 url = branch.remote.ReviewUrl(self.UserEmail)
1067 if url is None:
1068 raise UploadError('review not configured')
1069 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001070
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001071 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001072 rp = ['gerrit receive-pack']
1073 for e in people[0]:
1074 rp.append('--reviewer=%s' % sq(e))
1075 for e in people[1]:
1076 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001077 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001078
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001079 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001080
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001081 if dest_branch.startswith(R_HEADS):
1082 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001083
1084 upload_type = 'for'
1085 if draft:
1086 upload_type = 'drafts'
1087
1088 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1089 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001090 if auto_topic:
1091 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001092 if not url.startswith('ssh://'):
1093 rp = ['r=%s' % p for p in people[0]] + \
1094 ['cc=%s' % p for p in people[1]]
1095 if rp:
1096 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001097 cmd.append(ref_spec)
1098
Anthony King7bdac712014-07-16 12:56:40 +01001099 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001100 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
1102 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1103 self.bare_git.UpdateRef(R_PUB + branch.name,
1104 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001105 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
1107
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001108# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Julien Campergue335f5ef2013-10-16 11:02:35 +02001110 def _ExtractArchive(self, tarpath, path=None):
1111 """Extract the given tar on its current location
1112
1113 Args:
1114 - tarpath: The path to the actual tar file
1115
1116 """
1117 try:
1118 with tarfile.open(tarpath, 'r') as tar:
1119 tar.extractall(path=path)
1120 return True
1121 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001122 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001123 return False
1124
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001125 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001126 quiet=False,
1127 is_new=None,
1128 current_branch_only=False,
1129 force_sync=False,
1130 clone_bundle=True,
1131 no_tags=False,
1132 archive=False,
1133 optimized_fetch=False,
1134 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 """Perform only the network IO portion of the sync process.
1136 Local working directory/branch state is not affected.
1137 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001138 if archive and not isinstance(self, MetaProject):
1139 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001140 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001141 return False
1142
1143 name = self.relpath.replace('\\', '/')
1144 name = name.replace('/', '_')
1145 tarpath = '%s.tar' % name
1146 topdir = self.manifest.topdir
1147
1148 try:
1149 self._FetchArchive(tarpath, cwd=topdir)
1150 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001151 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001152 return False
1153
1154 # From now on, we only need absolute tarpath
1155 tarpath = os.path.join(topdir, tarpath)
1156
1157 if not self._ExtractArchive(tarpath, path=topdir):
1158 return False
1159 try:
1160 os.remove(tarpath)
1161 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001162 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001163 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001164 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001165 if is_new is None:
1166 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001167 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001168 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001169 else:
1170 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001172
1173 if is_new:
1174 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1175 try:
1176 fd = open(alt, 'rb')
1177 try:
1178 alt_dir = fd.readline().rstrip()
1179 finally:
1180 fd.close()
1181 except IOError:
1182 alt_dir = None
1183 else:
1184 alt_dir = None
1185
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001186 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001187 and alt_dir is None \
1188 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001189 is_new = False
1190
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001191 if not current_branch_only:
1192 if self.sync_c:
1193 current_branch_only = True
1194 elif not self.manifest._loaded:
1195 # Manifest cannot check defaults until it syncs.
1196 current_branch_only = False
1197 elif self.manifest.default.sync_c:
1198 current_branch_only = True
1199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001200 need_to_fetch = not (optimized_fetch and
1201 (ID_RE.match(self.revisionExpr) and
1202 self._CheckForSha1()))
1203 if (need_to_fetch and
1204 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1205 current_branch_only=current_branch_only,
1206 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001207 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001208
1209 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001210 self._InitMRef()
1211 else:
1212 self._InitMirrorHead()
1213 try:
1214 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1215 except OSError:
1216 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001218
1219 def PostRepoUpgrade(self):
1220 self._InitHooks()
1221
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001222 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001223 if self.manifest.isGitcClient:
1224 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001225 for copyfile in self.copyfiles:
1226 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001227 for linkfile in self.linkfiles:
1228 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229
Julien Camperguedd654222014-01-09 16:21:37 +01001230 def GetCommitRevisionId(self):
1231 """Get revisionId of a commit.
1232
1233 Use this method instead of GetRevisionId to get the id of the commit rather
1234 than the id of the current git object (for example, a tag)
1235
1236 """
1237 if not self.revisionExpr.startswith(R_TAGS):
1238 return self.GetRevisionId(self._allrefs)
1239
1240 try:
1241 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1242 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001243 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1244 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001245
David Pursehouse8a68ff92012-09-24 12:15:13 +09001246 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001247 if self.revisionId:
1248 return self.revisionId
1249
1250 rem = self.GetRemote(self.remote.name)
1251 rev = rem.ToLocal(self.revisionExpr)
1252
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 if all_refs is not None and rev in all_refs:
1254 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001255
1256 try:
1257 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1258 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001259 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1260 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001261
Kevin Degiabaa7f32014-11-12 11:27:45 -07001262 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 """Perform only the local IO portion of the sync process.
1264 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001266 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001267 all_refs = self.bare_ref.all
1268 self.CleanPublishedCache(all_refs)
1269 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001270
David Pursehouse1d947b32012-10-25 12:23:11 +09001271 def _doff():
1272 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001273 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001274
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001275 head = self.work_git.GetHead()
1276 if head.startswith(R_HEADS):
1277 branch = head[len(R_HEADS):]
1278 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001280 except KeyError:
1281 head = None
1282 else:
1283 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 # Currently on a detached HEAD. The user is assumed to
1287 # not have any local modifications worth worrying about.
1288 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001289 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001290 syncbuf.fail(self, _PriorSyncFailedError())
1291 return
1292
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001293 if head == revid:
1294 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001295 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001296 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001297 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001298 # The copy/linkfile config may have changed.
1299 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001300 return
1301 else:
1302 lost = self._revlist(not_rev(revid), HEAD)
1303 if lost:
1304 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001305
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001308 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 syncbuf.fail(self, e)
1310 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001311 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001314 if head == revid:
1315 # No changes; don't do anything further.
1316 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001317 # The copy/linkfile config may have changed.
1318 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001319 return
1320
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001323 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001325 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.info(self,
1328 "leaving %s; does not track upstream",
1329 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001331 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001332 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001333 syncbuf.fail(self, e)
1334 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001335 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001336 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001339 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001341 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 if not_merged:
1343 if upstream_gain:
1344 # The user has published this branch and some of those
1345 # commits are not yet merged upstream. We do not want
1346 # to rewrite the published commits so we punt.
1347 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001348 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001349 "branch %s is published (but not merged) and is now "
1350 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001351 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001352 elif pub == head:
1353 # All published commits are merged, and thus we are a
1354 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001355 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001356 syncbuf.later1(self, _doff)
1357 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001359 # Examine the local commits not in the remote. Find the
1360 # last one attributed to this user, if any.
1361 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001362 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 last_mine = None
1364 cnt_mine = 0
1365 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301366 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001367 if committer_email == self.UserEmail:
1368 last_mine = commit_id
1369 cnt_mine += 1
1370
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001371 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001372 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
1374 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001375 syncbuf.fail(self, _DirtyError())
1376 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 # If the upstream switched on us, warn the user.
1379 #
1380 if branch.merge != self.revisionExpr:
1381 if branch.merge and self.revisionExpr:
1382 syncbuf.info(self,
1383 'manifest switched %s...%s',
1384 branch.merge,
1385 self.revisionExpr)
1386 elif branch.merge:
1387 syncbuf.info(self,
1388 'manifest no longer tracks %s',
1389 branch.merge)
1390
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001391 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001393 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001395 syncbuf.info(self,
1396 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001397 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001399 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001400 if not ID_RE.match(self.revisionExpr):
1401 # in case of manifest sync the revisionExpr might be a SHA1
1402 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001403 if not branch.merge.startswith('refs/'):
1404 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405 branch.Save()
1406
Mike Pontillod3153822012-02-28 11:53:24 -08001407 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001408 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001409 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001410 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001411 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001412 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001414 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001415 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001416 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 syncbuf.fail(self, e)
1418 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001420 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001422 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423 # dest should already be an absolute path, but src is project relative
1424 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001425 abssrc = os.path.join(self.worktree, src)
1426 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001427
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001428 def AddLinkFile(self, src, dest, absdest):
1429 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001430 # make src relative path to dest
1431 absdestdir = os.path.dirname(absdest)
1432 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001433 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001434
James W. Mills24c13082012-04-12 15:04:13 -05001435 def AddAnnotation(self, name, value, keep):
1436 self.annotations.append(_Annotation(name, value, keep))
1437
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001438 def DownloadPatchSet(self, change_id, patch_id):
1439 """Download a single patch set of a single change to FETCH_HEAD.
1440 """
1441 remote = self.GetRemote(self.remote.name)
1442
1443 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001444 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001445 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001446 if GitCommand(self, cmd, bare=True).Wait() != 0:
1447 return None
1448 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001449 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001450 change_id,
1451 patch_id,
1452 self.bare_git.rev_parse('FETCH_HEAD'))
1453
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001454
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001455# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456
Simran Basib9a1b732015-08-20 12:19:28 -07001457 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458 """Create a new branch off the manifest's revision.
1459 """
Simran Basib9a1b732015-08-20 12:19:28 -07001460 if not branch_merge:
1461 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001462 head = self.work_git.GetHead()
1463 if head == (R_HEADS + name):
1464 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465
David Pursehouse8a68ff92012-09-24 12:15:13 +09001466 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001467 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001468 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001469 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001470 capture_stdout=True,
1471 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001472
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001473 branch = self.GetBranch(name)
1474 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001475 branch.merge = branch_merge
1476 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1477 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001478 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001479
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001480 if head.startswith(R_HEADS):
1481 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001482 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001483 except KeyError:
1484 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001485 if revid and head and revid == head:
1486 ref = os.path.join(self.gitdir, R_HEADS + name)
1487 try:
1488 os.makedirs(os.path.dirname(ref))
1489 except OSError:
1490 pass
1491 _lwrite(ref, '%s\n' % revid)
1492 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1493 'ref: %s%s\n' % (R_HEADS, name))
1494 branch.Save()
1495 return True
1496
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001497 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001498 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001499 capture_stdout=True,
1500 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001501 branch.Save()
1502 return True
1503 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504
Wink Saville02d79452009-04-10 13:01:24 -07001505 def CheckoutBranch(self, name):
1506 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001507
1508 Args:
1509 name: The name of the branch to checkout.
1510
1511 Returns:
1512 True if the checkout succeeded; False if it didn't; None if the branch
1513 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001514 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001515 rev = R_HEADS + name
1516 head = self.work_git.GetHead()
1517 if head == rev:
1518 # Already on the branch
1519 #
1520 return True
Wink Saville02d79452009-04-10 13:01:24 -07001521
David Pursehouse8a68ff92012-09-24 12:15:13 +09001522 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001523 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001524 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001525 except KeyError:
1526 # Branch does not exist in this project
1527 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001528 return None
Wink Saville02d79452009-04-10 13:01:24 -07001529
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001530 if head.startswith(R_HEADS):
1531 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001532 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001533 except KeyError:
1534 head = None
1535
1536 if head == revid:
1537 # Same revision; just update HEAD to point to the new
1538 # target branch, but otherwise take no other action.
1539 #
1540 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1541 'ref: %s%s\n' % (R_HEADS, name))
1542 return True
1543
1544 return GitCommand(self,
1545 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001546 capture_stdout=True,
1547 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001548
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001549 def AbandonBranch(self, name):
1550 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001551
1552 Args:
1553 name: The name of the branch to abandon.
1554
1555 Returns:
1556 True if the abandon succeeded; False if it didn't; None if the branch
1557 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001558 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001559 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001560 all_refs = self.bare_ref.all
1561 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001562 # Doesn't exist
1563 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001564
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001565 head = self.work_git.GetHead()
1566 if head == rev:
1567 # We can't destroy the branch while we are sitting
1568 # on it. Switch to a detached HEAD.
1569 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001570 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001571
David Pursehouse8a68ff92012-09-24 12:15:13 +09001572 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001573 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001574 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1575 '%s\n' % revid)
1576 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001577 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001578
1579 return GitCommand(self,
1580 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001581 capture_stdout=True,
1582 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584 def PruneHeads(self):
1585 """Prune any topic branches already merged into upstream.
1586 """
1587 cb = self.CurrentBranch
1588 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001589 left = self._allrefs
1590 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001591 if name.startswith(R_HEADS):
1592 name = name[len(R_HEADS):]
1593 if cb is None or name != cb:
1594 kill.append(name)
1595
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001596 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597 if cb is not None \
1598 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001599 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 self.work_git.DetachHead(HEAD)
1601 kill.append(cb)
1602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001604 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606 try:
1607 self.bare_git.DetachHead(rev)
1608
1609 b = ['branch', '-d']
1610 b.extend(kill)
1611 b = GitCommand(self, b, bare=True,
1612 capture_stdout=True,
1613 capture_stderr=True)
1614 b.Wait()
1615 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001616 if ID_RE.match(old):
1617 self.bare_git.DetachHead(old)
1618 else:
1619 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001620 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001621
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001622 for branch in kill:
1623 if (R_HEADS + branch) not in left:
1624 self.CleanPublishedCache()
1625 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001626
1627 if cb and cb not in kill:
1628 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001629 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001630
1631 kept = []
1632 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001633 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001634 branch = self.GetBranch(branch)
1635 base = branch.LocalMerge
1636 if not base:
1637 base = rev
1638 kept.append(ReviewableBranch(self, branch, base))
1639 return kept
1640
1641
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001642# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001643
1644 def GetRegisteredSubprojects(self):
1645 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001646
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001647 def rec(subprojects):
1648 if not subprojects:
1649 return
1650 result.extend(subprojects)
1651 for p in subprojects:
1652 rec(p.subprojects)
1653 rec(self.subprojects)
1654 return result
1655
1656 def _GetSubmodules(self):
1657 # Unfortunately we cannot call `git submodule status --recursive` here
1658 # because the working tree might not exist yet, and it cannot be used
1659 # without a working tree in its current implementation.
1660
1661 def get_submodules(gitdir, rev):
1662 # Parse .gitmodules for submodule sub_paths and sub_urls
1663 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1664 if not sub_paths:
1665 return []
1666 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1667 # revision of submodule repository
1668 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1669 submodules = []
1670 for sub_path, sub_url in zip(sub_paths, sub_urls):
1671 try:
1672 sub_rev = sub_revs[sub_path]
1673 except KeyError:
1674 # Ignore non-exist submodules
1675 continue
1676 submodules.append((sub_rev, sub_path, sub_url))
1677 return submodules
1678
1679 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1680 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001681
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001682 def parse_gitmodules(gitdir, rev):
1683 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1684 try:
Anthony King7bdac712014-07-16 12:56:40 +01001685 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1686 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001687 except GitError:
1688 return [], []
1689 if p.Wait() != 0:
1690 return [], []
1691
1692 gitmodules_lines = []
1693 fd, temp_gitmodules_path = tempfile.mkstemp()
1694 try:
1695 os.write(fd, p.stdout)
1696 os.close(fd)
1697 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001698 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1699 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001700 if p.Wait() != 0:
1701 return [], []
1702 gitmodules_lines = p.stdout.split('\n')
1703 except GitError:
1704 return [], []
1705 finally:
1706 os.remove(temp_gitmodules_path)
1707
1708 names = set()
1709 paths = {}
1710 urls = {}
1711 for line in gitmodules_lines:
1712 if not line:
1713 continue
1714 m = re_path.match(line)
1715 if m:
1716 names.add(m.group(1))
1717 paths[m.group(1)] = m.group(2)
1718 continue
1719 m = re_url.match(line)
1720 if m:
1721 names.add(m.group(1))
1722 urls[m.group(1)] = m.group(2)
1723 continue
1724 names = sorted(names)
1725 return ([paths.get(name, '') for name in names],
1726 [urls.get(name, '') for name in names])
1727
1728 def git_ls_tree(gitdir, rev, paths):
1729 cmd = ['ls-tree', rev, '--']
1730 cmd.extend(paths)
1731 try:
Anthony King7bdac712014-07-16 12:56:40 +01001732 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1733 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001734 except GitError:
1735 return []
1736 if p.Wait() != 0:
1737 return []
1738 objects = {}
1739 for line in p.stdout.split('\n'):
1740 if not line.strip():
1741 continue
1742 object_rev, object_path = line.split()[2:4]
1743 objects[object_path] = object_rev
1744 return objects
1745
1746 try:
1747 rev = self.GetRevisionId()
1748 except GitError:
1749 return []
1750 return get_submodules(self.gitdir, rev)
1751
1752 def GetDerivedSubprojects(self):
1753 result = []
1754 if not self.Exists:
1755 # If git repo does not exist yet, querying its submodules will
1756 # mess up its states; so return here.
1757 return result
1758 for rev, path, url in self._GetSubmodules():
1759 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001760 relpath, worktree, gitdir, objdir = \
1761 self.manifest.GetSubprojectPaths(self, name, path)
1762 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001763 if project:
1764 result.extend(project.GetDerivedSubprojects())
1765 continue
David James8d201162013-10-11 17:03:19 -07001766
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001767 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001768 url=url,
1769 review=self.remote.review,
1770 revision=self.remote.revision)
1771 subproject = Project(manifest=self.manifest,
1772 name=name,
1773 remote=remote,
1774 gitdir=gitdir,
1775 objdir=objdir,
1776 worktree=worktree,
1777 relpath=relpath,
1778 revisionExpr=self.revisionExpr,
1779 revisionId=rev,
1780 rebase=self.rebase,
1781 groups=self.groups,
1782 sync_c=self.sync_c,
1783 sync_s=self.sync_s,
1784 parent=self,
1785 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001786 result.append(subproject)
1787 result.extend(subproject.GetDerivedSubprojects())
1788 return result
1789
1790
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001791# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001792 def _CheckForSha1(self):
1793 try:
1794 # if revision (sha or tag) is not present then following function
1795 # throws an error.
1796 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1797 return True
1798 except GitError:
1799 # There is no such persistent revision. We have to fetch it.
1800 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801
Julien Campergue335f5ef2013-10-16 11:02:35 +02001802 def _FetchArchive(self, tarpath, cwd=None):
1803 cmd = ['archive', '-v', '-o', tarpath]
1804 cmd.append('--remote=%s' % self.remote.url)
1805 cmd.append('--prefix=%s/' % self.relpath)
1806 cmd.append(self.revisionExpr)
1807
1808 command = GitCommand(self, cmd, cwd=cwd,
1809 capture_stdout=True,
1810 capture_stderr=True)
1811
1812 if command.Wait() != 0:
1813 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1814
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001815 def _RemoteFetch(self, name=None,
1816 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001817 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001818 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001819 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001820 no_tags=False,
1821 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001822
1823 is_sha1 = False
1824 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001825 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001826
David Pursehouse9bc422f2014-04-15 10:28:56 +09001827 # The depth should not be used when fetching to a mirror because
1828 # it will result in a shallow repository that cannot be cloned or
1829 # fetched from.
1830 if not self.manifest.IsMirror:
1831 if self.clone_depth:
1832 depth = self.clone_depth
1833 else:
1834 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001835 # The repo project should never be synced with partial depth
1836 if self.relpath == '.repo/repo':
1837 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001838
Shawn Pearce69e04d82014-01-29 12:48:54 -08001839 if depth:
1840 current_branch_only = True
1841
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001842 if ID_RE.match(self.revisionExpr) is not None:
1843 is_sha1 = True
1844
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001845 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001846 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001847 # this is a tag and its sha1 value should never change
1848 tag_name = self.revisionExpr[len(R_TAGS):]
1849
1850 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001851 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001852 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001853 if is_sha1 and not depth:
1854 # When syncing a specific commit and --depth is not set:
1855 # * if upstream is explicitly specified and is not a sha1, fetch only
1856 # upstream as users expect only upstream to be fetch.
1857 # Note: The commit might not be in upstream in which case the sync
1858 # will fail.
1859 # * otherwise, fetch all branches to make sure we end up with the
1860 # specific commit.
1861 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001862
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863 if not name:
1864 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001865
1866 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001867 remote = self.GetRemote(name)
1868 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001869 ssh_proxy = True
1870
Shawn O. Pearce88443382010-10-08 10:02:09 +02001871 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001872 if alt_dir and 'objects' == os.path.basename(alt_dir):
1873 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1875 remote = self.GetRemote(name)
1876
David Pursehouse8a68ff92012-09-24 12:15:13 +09001877 all_refs = self.bare_ref.all
1878 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001879 tmp = set()
1880
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301881 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001882 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001883 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001884 all_refs[r] = ref_id
1885 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001886 continue
1887
David Pursehouse8a68ff92012-09-24 12:15:13 +09001888 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001889 continue
1890
David Pursehouse8a68ff92012-09-24 12:15:13 +09001891 r = 'refs/_alt/%s' % ref_id
1892 all_refs[r] = ref_id
1893 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001894 tmp.add(r)
1895
Shawn O. Pearce88443382010-10-08 10:02:09 +02001896 tmp_packed = ''
1897 old_packed = ''
1898
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301899 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001900 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001901 tmp_packed += line
1902 if r not in tmp:
1903 old_packed += line
1904
1905 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001906 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001907 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001908
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001909 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001910
Conley Owensf97e8382015-01-21 11:12:46 -08001911 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001912 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001913 else:
1914 # If this repo has shallow objects, then we don't know which refs have
1915 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1916 # do this with projects that don't have shallow objects, since it is less
1917 # efficient.
1918 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1919 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001920
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001921 if quiet:
1922 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001923 if not self.worktree:
1924 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001925 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001926
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001927 # If using depth then we should not get all the tags since they may
1928 # be outside of the depth.
1929 if no_tags or depth:
1930 cmd.append('--no-tags')
1931 else:
1932 cmd.append('--tags')
1933
David Pursehouse74cfd272015-10-14 10:50:15 +09001934 if prune:
1935 cmd.append('--prune')
1936
Conley Owens80b87fe2014-05-09 17:13:44 -07001937 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001938 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001939 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001940 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001941 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001942 spec.append('tag')
1943 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001944
David Pursehouse403b64e2015-04-27 10:41:33 +09001945 if not self.manifest.IsMirror:
1946 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001947 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001948 # Shallow checkout of a specific commit, fetch from that commit and not
1949 # the heads only as the commit might be deeper in the history.
1950 spec.append(branch)
1951 else:
1952 if is_sha1:
1953 branch = self.upstream
1954 if branch is not None and branch.strip():
1955 if not branch.startswith('refs/'):
1956 branch = R_HEADS + branch
1957 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001958 cmd.extend(spec)
1959
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001960 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001961 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001962 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001963 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001964 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001965 ok = True
1966 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001967 # If needed, run the 'git remote prune' the first time through the loop
1968 elif (not _i and
1969 "error:" in gitcmd.stderr and
1970 "git remote prune" in gitcmd.stderr):
1971 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001972 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001973 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001974 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001975 break
1976 continue
Brian Harring14a66742012-09-28 20:21:57 -07001977 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001978 # Exit code 128 means "couldn't find the ref you asked for"; if we're
1979 # in sha1 mode, we just tried sync'ing from the upstream field; it
1980 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07001981 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001982 elif ret < 0:
1983 # Git died with a signal, exit immediately
1984 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001985 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001986
1987 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001988 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001989 if old_packed != '':
1990 _lwrite(packed_refs, old_packed)
1991 else:
1992 os.remove(packed_refs)
1993 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001994
1995 if is_sha1 and current_branch_only and self.upstream:
1996 # We just synced the upstream given branch; verify we
1997 # got what we wanted, else trigger a second run of all
1998 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001999 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002000 if not depth:
2001 # Avoid infinite recursion when depth is True (since depth implies
2002 # current_branch_only)
2003 return self._RemoteFetch(name=name, current_branch_only=False,
2004 initial=False, quiet=quiet, alt_dir=alt_dir)
2005 if self.clone_depth:
2006 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002007 return self._RemoteFetch(name=name,
2008 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002009 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002010
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002011 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002012
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002013 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002014 if initial and \
2015 (self.manifest.manifestProject.config.GetString('repo.depth') or
2016 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002017 return False
2018
2019 remote = self.GetRemote(self.remote.name)
2020 bundle_url = remote.url + '/clone.bundle'
2021 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002022 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2023 'persistent-http',
2024 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002025 return False
2026
2027 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2028 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2029
2030 exist_dst = os.path.exists(bundle_dst)
2031 exist_tmp = os.path.exists(bundle_tmp)
2032
2033 if not initial and not exist_dst and not exist_tmp:
2034 return False
2035
2036 if not exist_dst:
2037 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2038 if not exist_dst:
2039 return False
2040
2041 cmd = ['fetch']
2042 if quiet:
2043 cmd.append('--quiet')
2044 if not self.worktree:
2045 cmd.append('--update-head-ok')
2046 cmd.append(bundle_dst)
2047 for f in remote.fetch:
2048 cmd.append(str(f))
2049 cmd.append('refs/tags/*:refs/tags/*')
2050
2051 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002052 if os.path.exists(bundle_dst):
2053 os.remove(bundle_dst)
2054 if os.path.exists(bundle_tmp):
2055 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002058 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002059 if os.path.exists(dstPath):
2060 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002061
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002062 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002063 if quiet:
2064 cmd += ['--silent']
2065 if os.path.exists(tmpPath):
2066 size = os.stat(tmpPath).st_size
2067 if size >= 1024:
2068 cmd += ['--continue-at', '%d' % (size,)]
2069 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002070 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002071 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2072 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002073 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002074 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002075 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002076 if srcUrl.startswith('persistent-'):
2077 srcUrl = srcUrl[len('persistent-'):]
2078 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002079
Dave Borowitz137d0132015-01-02 11:12:54 -08002080 if IsTrace():
2081 Trace('%s', ' '.join(cmd))
2082 try:
2083 proc = subprocess.Popen(cmd)
2084 except OSError:
2085 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002086
Dave Borowitz137d0132015-01-02 11:12:54 -08002087 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002088
Dave Borowitz137d0132015-01-02 11:12:54 -08002089 if curlret == 22:
2090 # From curl man page:
2091 # 22: HTTP page not retrieved. The requested url was not found or
2092 # returned another error with the HTTP error code being 400 or above.
2093 # This return code only appears if -f, --fail is used.
2094 if not quiet:
2095 print("Server does not provide clone.bundle; ignoring.",
2096 file=sys.stderr)
2097 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002098
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002099 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002100 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002101 os.rename(tmpPath, dstPath)
2102 return True
2103 else:
2104 os.remove(tmpPath)
2105 return False
2106 else:
2107 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002108
Kris Giesingc8d882a2014-12-23 13:02:32 -08002109 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002110 try:
2111 with open(path) as f:
2112 if f.read(16) == '# v2 git bundle\n':
2113 return True
2114 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002115 if not quiet:
2116 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002117 return False
2118 except OSError:
2119 return False
2120
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121 def _Checkout(self, rev, quiet=False):
2122 cmd = ['checkout']
2123 if quiet:
2124 cmd.append('-q')
2125 cmd.append(rev)
2126 cmd.append('--')
2127 if GitCommand(self, cmd).Wait() != 0:
2128 if self._allrefs:
2129 raise GitError('%s checkout %s ' % (self.name, rev))
2130
Anthony King7bdac712014-07-16 12:56:40 +01002131 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002132 cmd = ['cherry-pick']
2133 cmd.append(rev)
2134 cmd.append('--')
2135 if GitCommand(self, cmd).Wait() != 0:
2136 if self._allrefs:
2137 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2138
Anthony King7bdac712014-07-16 12:56:40 +01002139 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002140 cmd = ['revert']
2141 cmd.append('--no-edit')
2142 cmd.append(rev)
2143 cmd.append('--')
2144 if GitCommand(self, cmd).Wait() != 0:
2145 if self._allrefs:
2146 raise GitError('%s revert %s ' % (self.name, rev))
2147
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148 def _ResetHard(self, rev, quiet=True):
2149 cmd = ['reset', '--hard']
2150 if quiet:
2151 cmd.append('-q')
2152 cmd.append(rev)
2153 if GitCommand(self, cmd).Wait() != 0:
2154 raise GitError('%s reset --hard %s ' % (self.name, rev))
2155
Anthony King7bdac712014-07-16 12:56:40 +01002156 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002157 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 if onto is not None:
2159 cmd.extend(['--onto', onto])
2160 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002161 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 raise GitError('%s rebase %s ' % (self.name, upstream))
2163
Pierre Tardy3d125942012-05-04 12:18:12 +02002164 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002165 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002166 if ffonly:
2167 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 if GitCommand(self, cmd).Wait() != 0:
2169 raise GitError('%s merge %s ' % (self.name, head))
2170
Kevin Degiabaa7f32014-11-12 11:27:45 -07002171 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002172 init_git_dir = not os.path.exists(self.gitdir)
2173 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002174 try:
2175 # Initialize the bare repository, which contains all of the objects.
2176 if init_obj_dir:
2177 os.makedirs(self.objdir)
2178 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002179
Kevin Degib1a07b82015-07-27 13:33:43 -06002180 # If we have a separate directory to hold refs, initialize it as well.
2181 if self.objdir != self.gitdir:
2182 if init_git_dir:
2183 os.makedirs(self.gitdir)
2184
2185 if init_obj_dir or init_git_dir:
2186 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2187 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002188 try:
2189 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2190 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002191 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002192 print("Retrying clone after deleting %s" %
2193 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002194 try:
2195 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002196 if self.worktree and os.path.exists(os.path.realpath
2197 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002198 shutil.rmtree(os.path.realpath(self.worktree))
2199 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2200 except:
2201 raise e
2202 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002203
Kevin Degi384b3c52014-10-16 16:02:58 -06002204 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002205 mp = self.manifest.manifestProject
2206 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002207
Kevin Degib1a07b82015-07-27 13:33:43 -06002208 if ref_dir or mirror_git:
2209 if not mirror_git:
2210 mirror_git = os.path.join(ref_dir, self.name + '.git')
2211 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2212 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002213
Kevin Degib1a07b82015-07-27 13:33:43 -06002214 if os.path.exists(mirror_git):
2215 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002216
Kevin Degib1a07b82015-07-27 13:33:43 -06002217 elif os.path.exists(repo_git):
2218 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002219
Kevin Degib1a07b82015-07-27 13:33:43 -06002220 else:
2221 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002222
Kevin Degib1a07b82015-07-27 13:33:43 -06002223 if ref_dir:
2224 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2225 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002226
Kevin Degib1a07b82015-07-27 13:33:43 -06002227 self._UpdateHooks()
2228
2229 m = self.manifest.manifestProject.config
2230 for key in ['user.name', 'user.email']:
2231 if m.Has(key, include_defaults=False):
2232 self.config.SetString(key, m.GetString(key))
2233 if self.manifest.IsMirror:
2234 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002235 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002236 self.config.SetString('core.bare', None)
2237 except Exception:
2238 if init_obj_dir and os.path.exists(self.objdir):
2239 shutil.rmtree(self.objdir)
2240 if init_git_dir and os.path.exists(self.gitdir):
2241 shutil.rmtree(self.gitdir)
2242 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002243
Jimmie Westera0444582012-10-24 13:44:42 +02002244 def _UpdateHooks(self):
2245 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002246 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002248 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002249 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002250 if not os.path.exists(hooks):
2251 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002252 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002253 name = os.path.basename(stock_hook)
2254
Victor Boivie65e0f352011-04-18 11:23:29 +02002255 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002256 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002257 # Don't install a Gerrit Code Review hook if this
2258 # project does not appear to use it for reviews.
2259 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002260 # Since the manifest project is one of those, but also
2261 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002262 continue
2263
2264 dst = os.path.join(hooks, name)
2265 if os.path.islink(dst):
2266 continue
2267 if os.path.exists(dst):
2268 if filecmp.cmp(stock_hook, dst, shallow=False):
2269 os.remove(dst)
2270 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002271 _warn("%s: Not replacing locally modified %s hook",
2272 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002273 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002274 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002275 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002276 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002277 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002278 raise GitError('filesystem must support symlinks')
2279 else:
2280 raise
2281
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002282 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002283 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002284 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002285 remote.url = self.remote.url
2286 remote.review = self.remote.review
2287 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002289 if self.worktree:
2290 remote.ResetFetch(mirror=False)
2291 else:
2292 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293 remote.Save()
2294
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002295 def _InitMRef(self):
2296 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002297 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002298
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002299 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002300 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002301
2302 def _InitAnyMRef(self, ref):
2303 cur = self.bare_ref.symref(ref)
2304
2305 if self.revisionId:
2306 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2307 msg = 'manifest set to %s' % self.revisionId
2308 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002309 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002310 else:
2311 remote = self.GetRemote(self.remote.name)
2312 dst = remote.ToLocal(self.revisionExpr)
2313 if cur != dst:
2314 msg = 'manifest set to %s' % self.revisionExpr
2315 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002316
Kevin Degi384b3c52014-10-16 16:02:58 -06002317 def _CheckDirReference(self, srcdir, destdir, share_refs):
2318 symlink_files = self.shareable_files
2319 symlink_dirs = self.shareable_dirs
2320 if share_refs:
2321 symlink_files += self.working_tree_files
2322 symlink_dirs += self.working_tree_dirs
2323 to_symlink = symlink_files + symlink_dirs
2324 for name in set(to_symlink):
2325 dst = os.path.realpath(os.path.join(destdir, name))
2326 if os.path.lexists(dst):
2327 src = os.path.realpath(os.path.join(srcdir, name))
2328 # Fail if the links are pointing to the wrong place
2329 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002330 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002331 'work tree. If you\'re comfortable with the '
2332 'possibility of losing the work tree\'s git metadata,'
2333 ' use `repo sync --force-sync {0}` to '
2334 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002335
David James8d201162013-10-11 17:03:19 -07002336 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2337 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2338
2339 Args:
2340 gitdir: The bare git repository. Must already be initialized.
2341 dotgit: The repository you would like to initialize.
2342 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2343 Only one work tree can store refs under a given |gitdir|.
2344 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2345 This saves you the effort of initializing |dotgit| yourself.
2346 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002347 symlink_files = self.shareable_files
2348 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002349 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002350 symlink_files += self.working_tree_files
2351 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002352 to_symlink = symlink_files + symlink_dirs
2353
2354 to_copy = []
2355 if copy_all:
2356 to_copy = os.listdir(gitdir)
2357
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002358 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002359 for name in set(to_copy).union(to_symlink):
2360 try:
2361 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002362 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002363
Kevin Degi384b3c52014-10-16 16:02:58 -06002364 if os.path.lexists(dst):
2365 continue
David James8d201162013-10-11 17:03:19 -07002366
2367 # If the source dir doesn't exist, create an empty dir.
2368 if name in symlink_dirs and not os.path.lexists(src):
2369 os.makedirs(src)
2370
Conley Owens80b87fe2014-05-09 17:13:44 -07002371 # If the source file doesn't exist, ensure the destination
2372 # file doesn't either.
2373 if name in symlink_files and not os.path.lexists(src):
2374 try:
2375 os.remove(dst)
2376 except OSError:
2377 pass
2378
David James8d201162013-10-11 17:03:19 -07002379 if name in to_symlink:
2380 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2381 elif copy_all and not os.path.islink(dst):
2382 if os.path.isdir(src):
2383 shutil.copytree(src, dst)
2384 elif os.path.isfile(src):
2385 shutil.copy(src, dst)
2386 except OSError as e:
2387 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002388 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002389 else:
2390 raise
2391
Kevin Degiabaa7f32014-11-12 11:27:45 -07002392 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002394 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002395 try:
2396 if init_dotgit:
2397 os.makedirs(dotgit)
2398 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2399 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400
Kevin Degiabaa7f32014-11-12 11:27:45 -07002401 try:
2402 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2403 except GitError as e:
2404 if force_sync:
2405 try:
2406 shutil.rmtree(dotgit)
2407 return self._InitWorkTree(force_sync=False)
2408 except:
2409 raise e
2410 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002411
Kevin Degib1a07b82015-07-27 13:33:43 -06002412 if init_dotgit:
2413 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002414
Kevin Degib1a07b82015-07-27 13:33:43 -06002415 cmd = ['read-tree', '--reset', '-u']
2416 cmd.append('-v')
2417 cmd.append(HEAD)
2418 if GitCommand(self, cmd).Wait() != 0:
2419 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002420
Kevin Degib1a07b82015-07-27 13:33:43 -06002421 self._CopyAndLinkFiles()
2422 except Exception:
2423 if init_dotgit:
2424 shutil.rmtree(dotgit)
2425 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002426
2427 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002428 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002430 def _revlist(self, *args, **kw):
2431 a = []
2432 a.extend(args)
2433 a.append('--')
2434 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435
2436 @property
2437 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002438 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439
Julien Camperguedd654222014-01-09 16:21:37 +01002440 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2441 """Get logs between two revisions of this project."""
2442 comp = '..'
2443 if rev1:
2444 revs = [rev1]
2445 if rev2:
2446 revs.extend([comp, rev2])
2447 cmd = ['log', ''.join(revs)]
2448 out = DiffColoring(self.config)
2449 if out.is_on and color:
2450 cmd.append('--color')
2451 if oneline:
2452 cmd.append('--oneline')
2453
2454 try:
2455 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2456 if log.Wait() == 0:
2457 return log.stdout
2458 except GitError:
2459 # worktree may not exist if groups changed for example. In that case,
2460 # try in gitdir instead.
2461 if not os.path.exists(self.worktree):
2462 return self.bare_git.log(*cmd[1:])
2463 else:
2464 raise
2465 return None
2466
2467 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2468 """Get the list of logs from this revision to given revisionId"""
2469 logs = {}
2470 selfId = self.GetRevisionId(self._allrefs)
2471 toId = toProject.GetRevisionId(toProject._allrefs)
2472
2473 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2474 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2475 return logs
2476
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002478
David James8d201162013-10-11 17:03:19 -07002479 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480 self._project = project
2481 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002482 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002483
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484 def LsOthers(self):
2485 p = GitCommand(self._project,
2486 ['ls-files',
2487 '-z',
2488 '--others',
2489 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002490 bare=False,
David James8d201162013-10-11 17:03:19 -07002491 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002492 capture_stdout=True,
2493 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002494 if p.Wait() == 0:
2495 out = p.stdout
2496 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002497 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002498 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002499 return []
2500
2501 def DiffZ(self, name, *args):
2502 cmd = [name]
2503 cmd.append('-z')
2504 cmd.extend(args)
2505 p = GitCommand(self._project,
2506 cmd,
David James8d201162013-10-11 17:03:19 -07002507 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002508 bare=False,
2509 capture_stdout=True,
2510 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002511 try:
2512 out = p.process.stdout.read()
2513 r = {}
2514 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002515 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002516 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002517 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002518 info = next(out)
2519 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002520 except StopIteration:
2521 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002522
2523 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002524
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002525 def __init__(self, path, omode, nmode, oid, nid, state):
2526 self.path = path
2527 self.src_path = None
2528 self.old_mode = omode
2529 self.new_mode = nmode
2530 self.old_id = oid
2531 self.new_id = nid
2532
2533 if len(state) == 1:
2534 self.status = state
2535 self.level = None
2536 else:
2537 self.status = state[:1]
2538 self.level = state[1:]
2539 while self.level.startswith('0'):
2540 self.level = self.level[1:]
2541
2542 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002543 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002544 if info.status in ('R', 'C'):
2545 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002546 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002547 r[info.path] = info
2548 return r
2549 finally:
2550 p.Wait()
2551
2552 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002553 if self._bare:
2554 path = os.path.join(self._project.gitdir, HEAD)
2555 else:
2556 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002557 try:
2558 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002559 except IOError as e:
2560 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002561 try:
2562 line = fd.read()
2563 finally:
2564 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302565 try:
2566 line = line.decode()
2567 except AttributeError:
2568 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002569 if line.startswith('ref: '):
2570 return line[5:-1]
2571 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002572
2573 def SetHead(self, ref, message=None):
2574 cmdv = []
2575 if message is not None:
2576 cmdv.extend(['-m', message])
2577 cmdv.append(HEAD)
2578 cmdv.append(ref)
2579 self.symbolic_ref(*cmdv)
2580
2581 def DetachHead(self, new, message=None):
2582 cmdv = ['--no-deref']
2583 if message is not None:
2584 cmdv.extend(['-m', message])
2585 cmdv.append(HEAD)
2586 cmdv.append(new)
2587 self.update_ref(*cmdv)
2588
2589 def UpdateRef(self, name, new, old=None,
2590 message=None,
2591 detach=False):
2592 cmdv = []
2593 if message is not None:
2594 cmdv.extend(['-m', message])
2595 if detach:
2596 cmdv.append('--no-deref')
2597 cmdv.append(name)
2598 cmdv.append(new)
2599 if old is not None:
2600 cmdv.append(old)
2601 self.update_ref(*cmdv)
2602
2603 def DeleteRef(self, name, old=None):
2604 if not old:
2605 old = self.rev_parse(name)
2606 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002607 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002609 def rev_list(self, *args, **kw):
2610 if 'format' in kw:
2611 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2612 else:
2613 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614 cmdv.extend(args)
2615 p = GitCommand(self._project,
2616 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002617 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002618 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002619 capture_stdout=True,
2620 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002621 r = []
2622 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002623 if line[-1] == '\n':
2624 line = line[:-1]
2625 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002626 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002627 raise GitError('%s rev-list %s: %s' %
2628 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002629 return r
2630
2631 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002632 """Allow arbitrary git commands using pythonic syntax.
2633
2634 This allows you to do things like:
2635 git_obj.rev_parse('HEAD')
2636
2637 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2638 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002639 Any other positional arguments will be passed to the git command, and the
2640 following keyword arguments are supported:
2641 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002642
2643 Args:
2644 name: The name of the git command to call. Any '_' characters will
2645 be replaced with '-'.
2646
2647 Returns:
2648 A callable object that will try to call git with the named command.
2649 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002651
Dave Borowitz091f8932012-10-23 17:01:04 -07002652 def runner(*args, **kwargs):
2653 cmdv = []
2654 config = kwargs.pop('config', None)
2655 for k in kwargs:
2656 raise TypeError('%s() got an unexpected keyword argument %r'
2657 % (name, k))
2658 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002659 if not git_require((1, 7, 2)):
2660 raise ValueError('cannot set config on command line for %s()'
2661 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302662 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002663 cmdv.append('-c')
2664 cmdv.append('%s=%s' % (k, v))
2665 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002666 cmdv.extend(args)
2667 p = GitCommand(self._project,
2668 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002669 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002670 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002671 capture_stdout=True,
2672 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002674 raise GitError('%s %s: %s' %
2675 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002676 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302677 try:
Conley Owensedd01512013-09-26 12:59:58 -07002678 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302679 except AttributeError:
2680 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002681 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2682 return r[:-1]
2683 return r
2684 return runner
2685
2686
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002687class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002688
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002689 def __str__(self):
2690 return 'prior sync failed; rebase still in progress'
2691
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002692
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002693class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002694
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002695 def __str__(self):
2696 return 'contains uncommitted changes'
2697
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002698
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002699class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002700
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002701 def __init__(self, project, text):
2702 self.project = project
2703 self.text = text
2704
2705 def Print(self, syncbuf):
2706 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2707 syncbuf.out.nl()
2708
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002709
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002710class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002711
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002712 def __init__(self, project, why):
2713 self.project = project
2714 self.why = why
2715
2716 def Print(self, syncbuf):
2717 syncbuf.out.fail('error: %s/: %s',
2718 self.project.relpath,
2719 str(self.why))
2720 syncbuf.out.nl()
2721
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002722
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002723class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002724
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002725 def __init__(self, project, action):
2726 self.project = project
2727 self.action = action
2728
2729 def Run(self, syncbuf):
2730 out = syncbuf.out
2731 out.project('project %s/', self.project.relpath)
2732 out.nl()
2733 try:
2734 self.action()
2735 out.nl()
2736 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002737 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002738 out.nl()
2739 return False
2740
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002741
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002742class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002743
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002744 def __init__(self, config):
2745 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002746 self.project = self.printer('header', attr='bold')
2747 self.info = self.printer('info')
2748 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002749
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002750
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002751class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002752
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002753 def __init__(self, config, detach_head=False):
2754 self._messages = []
2755 self._failures = []
2756 self._later_queue1 = []
2757 self._later_queue2 = []
2758
2759 self.out = _SyncColoring(config)
2760 self.out.redirect(sys.stderr)
2761
2762 self.detach_head = detach_head
2763 self.clean = True
2764
2765 def info(self, project, fmt, *args):
2766 self._messages.append(_InfoMessage(project, fmt % args))
2767
2768 def fail(self, project, err=None):
2769 self._failures.append(_Failure(project, err))
2770 self.clean = False
2771
2772 def later1(self, project, what):
2773 self._later_queue1.append(_Later(project, what))
2774
2775 def later2(self, project, what):
2776 self._later_queue2.append(_Later(project, what))
2777
2778 def Finish(self):
2779 self._PrintMessages()
2780 self._RunLater()
2781 self._PrintMessages()
2782 return self.clean
2783
2784 def _RunLater(self):
2785 for q in ['_later_queue1', '_later_queue2']:
2786 if not self._RunQueue(q):
2787 return
2788
2789 def _RunQueue(self, queue):
2790 for m in getattr(self, queue):
2791 if not m.Run(self):
2792 self.clean = False
2793 return False
2794 setattr(self, queue, [])
2795 return True
2796
2797 def _PrintMessages(self):
2798 for m in self._messages:
2799 m.Print(self)
2800 for m in self._failures:
2801 m.Print(self)
2802
2803 self._messages = []
2804 self._failures = []
2805
2806
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002807class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002808
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809 """A special project housed under .repo.
2810 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002811
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002813 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002814 manifest=manifest,
2815 name=name,
2816 gitdir=gitdir,
2817 objdir=gitdir,
2818 worktree=worktree,
2819 remote=RemoteSpec('origin'),
2820 relpath='.repo/%s' % name,
2821 revisionExpr='refs/heads/master',
2822 revisionId=None,
2823 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002824
2825 def PreSync(self):
2826 if self.Exists:
2827 cb = self.CurrentBranch
2828 if cb:
2829 base = self.GetBranch(cb).merge
2830 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002831 self.revisionExpr = base
2832 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002833
Anthony King7bdac712014-07-16 12:56:40 +01002834 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002835 """ Prepare MetaProject for manifest branch switch
2836 """
2837
2838 # detach and delete manifest branch, allowing a new
2839 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002840 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002841 self.Sync_LocalHalf(syncbuf)
2842 syncbuf.Finish()
2843
2844 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002845 ['update-ref', '-d', 'refs/heads/default'],
2846 capture_stdout=True,
2847 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002848
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002849 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002850 def LastFetch(self):
2851 try:
2852 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2853 return os.path.getmtime(fh)
2854 except OSError:
2855 return 0
2856
2857 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002858 def HasChanges(self):
2859 """Has the remote received new commits not yet checked out?
2860 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002861 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002862 return False
2863
David Pursehouse8a68ff92012-09-24 12:15:13 +09002864 all_refs = self.bare_ref.all
2865 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002866 head = self.work_git.GetHead()
2867 if head.startswith(R_HEADS):
2868 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002869 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002870 except KeyError:
2871 head = None
2872
2873 if revid == head:
2874 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002875 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002876 return True
2877 return False