blob: 75c5e5e8ab7e72cefcd8495b547d9d4ce2cfb2d1 [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
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import 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
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070026import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070027
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070029from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070030from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090031from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080032from error import ManifestInvalidRevisionError
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070033from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Shawn O. Pearced237b692009-04-17 18:49:50 -070035from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070037def _lwrite(path, content):
38 lock = '%s.lock' % path
39
40 fd = open(lock, 'wb')
41 try:
42 fd.write(content)
43 finally:
44 fd.close()
45
46 try:
47 os.rename(lock, path)
48 except OSError:
49 os.remove(lock)
50 raise
51
Shawn O. Pearce48244782009-04-16 08:25:57 -070052def _error(fmt, *args):
53 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070054 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070055
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056def not_rev(r):
57 return '^' + r
58
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080059def sq(r):
60 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080061
Doug Anderson8ced8642011-01-10 14:16:30 -080062_project_hook_list = None
63def _ProjectHooks():
64 """List the hooks present in the 'hooks' directory.
65
66 These hooks are project hooks and are copied to the '.git/hooks' directory
67 of all subprojects.
68
69 This function caches the list of hooks (based on the contents of the
70 'repo/hooks' directory) on the first call.
71
72 Returns:
73 A list of absolute paths to all of the files in the hooks directory.
74 """
75 global _project_hook_list
76 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080077 d = os.path.abspath(os.path.dirname(__file__))
78 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080079 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
80 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080082
Shawn O. Pearce632768b2008-10-23 11:58:52 -070083class DownloadedChange(object):
84 _commit_cache = None
85
86 def __init__(self, project, base, change_id, ps_id, commit):
87 self.project = project
88 self.base = base
89 self.change_id = change_id
90 self.ps_id = ps_id
91 self.commit = commit
92
93 @property
94 def commits(self):
95 if self._commit_cache is None:
96 self._commit_cache = self.project.bare_git.rev_list(
97 '--abbrev=8',
98 '--abbrev-commit',
99 '--pretty=oneline',
100 '--reverse',
101 '--date-order',
102 not_rev(self.base),
103 self.commit,
104 '--')
105 return self._commit_cache
106
107
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108class ReviewableBranch(object):
109 _commit_cache = None
110
111 def __init__(self, project, branch, base):
112 self.project = project
113 self.branch = branch
114 self.base = base
115
116 @property
117 def name(self):
118 return self.branch.name
119
120 @property
121 def commits(self):
122 if self._commit_cache is None:
123 self._commit_cache = self.project.bare_git.rev_list(
124 '--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 R_HEADS + self.name,
131 '--')
132 return self._commit_cache
133
134 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800135 def unabbrev_commits(self):
136 r = dict()
137 for commit in self.project.bare_git.rev_list(
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--'):
141 r[commit[0:8]] = commit
142 return r
143
144 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700145 def date(self):
146 return self.project.bare_git.log(
147 '--pretty=format:%cd',
148 '-n', '1',
149 R_HEADS + self.name,
150 '--')
151
Brian Harring435370c2012-07-28 15:37:04 -0700152 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800153 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700154 people,
Brian Harring435370c2012-07-28 15:37:04 -0700155 auto_topic=auto_topic,
156 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700158 def GetPublishedRefs(self):
159 refs = {}
160 output = self.project.bare_git.ls_remote(
161 self.branch.remote.SshReviewUrl(self.project.UserEmail),
162 'refs/changes/*')
163 for line in output.split('\n'):
164 try:
165 (sha, ref) = line.split()
166 refs[sha] = ref
167 except ValueError:
168 pass
169
170 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
172class StatusColoring(Coloring):
173 def __init__(self, config):
174 Coloring.__init__(self, config, 'status')
175 self.project = self.printer('header', attr = 'bold')
176 self.branch = self.printer('header', attr = 'bold')
177 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700178 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179
180 self.added = self.printer('added', fg = 'green')
181 self.changed = self.printer('changed', fg = 'red')
182 self.untracked = self.printer('untracked', fg = 'red')
183
184
185class DiffColoring(Coloring):
186 def __init__(self, config):
187 Coloring.__init__(self, config, 'diff')
188 self.project = self.printer('header', attr = 'bold')
189
James W. Mills24c13082012-04-12 15:04:13 -0500190class _Annotation:
191 def __init__(self, name, value, keep):
192 self.name = name
193 self.value = value
194 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800197 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198 self.src = src
199 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800200 self.abs_src = abssrc
201 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202
203 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800204 src = self.abs_src
205 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 # copy file if it does not exist or is out of date
207 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
208 try:
209 # remove existing file first, since it might be read-only
210 if os.path.exists(dest):
211 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400212 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200213 dest_dir = os.path.dirname(dest)
214 if not os.path.isdir(dest_dir):
215 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 shutil.copy(src, dest)
217 # make the file read-only
218 mode = os.stat(dest)[stat.ST_MODE]
219 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
220 os.chmod(dest, mode)
221 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700222 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700224class RemoteSpec(object):
225 def __init__(self,
226 name,
227 url = None,
228 review = None):
229 self.name = name
230 self.url = url
231 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Doug Anderson37282b42011-03-04 11:54:18 -0800233class RepoHook(object):
234 """A RepoHook contains information about a script to run as a hook.
235
236 Hooks are used to run a python script before running an upload (for instance,
237 to run presubmit checks). Eventually, we may have hooks for other actions.
238
239 This shouldn't be confused with files in the 'repo/hooks' directory. Those
240 files are copied into each '.git/hooks' folder for each project. Repo-level
241 hooks are associated instead with repo actions.
242
243 Hooks are always python. When a hook is run, we will load the hook into the
244 interpreter and execute its main() function.
245 """
246 def __init__(self,
247 hook_type,
248 hooks_project,
249 topdir,
250 abort_if_user_denies=False):
251 """RepoHook constructor.
252
253 Params:
254 hook_type: A string representing the type of hook. This is also used
255 to figure out the name of the file containing the hook. For
256 example: 'pre-upload'.
257 hooks_project: The project containing the repo hooks. If you have a
258 manifest, this is manifest.repo_hooks_project. OK if this is None,
259 which will make the hook a no-op.
260 topdir: Repo's top directory (the one containing the .repo directory).
261 Scripts will run with CWD as this directory. If you have a manifest,
262 this is manifest.topdir
263 abort_if_user_denies: If True, we'll throw a HookError() if the user
264 doesn't allow us to run the hook.
265 """
266 self._hook_type = hook_type
267 self._hooks_project = hooks_project
268 self._topdir = topdir
269 self._abort_if_user_denies = abort_if_user_denies
270
271 # Store the full path to the script for convenience.
272 if self._hooks_project:
273 self._script_fullpath = os.path.join(self._hooks_project.worktree,
274 self._hook_type + '.py')
275 else:
276 self._script_fullpath = None
277
278 def _GetHash(self):
279 """Return a hash of the contents of the hooks directory.
280
281 We'll just use git to do this. This hash has the property that if anything
282 changes in the directory we will return a different has.
283
284 SECURITY CONSIDERATION:
285 This hash only represents the contents of files in the hook directory, not
286 any other files imported or called by hooks. Changes to imported files
287 can change the script behavior without affecting the hash.
288
289 Returns:
290 A string representing the hash. This will always be ASCII so that it can
291 be printed to the user easily.
292 """
293 assert self._hooks_project, "Must have hooks to calculate their hash."
294
295 # We will use the work_git object rather than just calling GetRevisionId().
296 # That gives us a hash of the latest checked in version of the files that
297 # the user will actually be executing. Specifically, GetRevisionId()
298 # doesn't appear to change even if a user checks out a different version
299 # of the hooks repo (via git checkout) nor if a user commits their own revs.
300 #
301 # NOTE: Local (non-committed) changes will not be factored into this hash.
302 # I think this is OK, since we're really only worried about warning the user
303 # about upstream changes.
304 return self._hooks_project.work_git.rev_parse('HEAD')
305
306 def _GetMustVerb(self):
307 """Return 'must' if the hook is required; 'should' if not."""
308 if self._abort_if_user_denies:
309 return 'must'
310 else:
311 return 'should'
312
313 def _CheckForHookApproval(self):
314 """Check to see whether this hook has been approved.
315
316 We'll look at the hash of all of the hooks. If this matches the hash that
317 the user last approved, we're done. If it doesn't, we'll ask the user
318 about approval.
319
320 Note that we ask permission for each individual hook even though we use
321 the hash of all hooks when detecting changes. We'd like the user to be
322 able to approve / deny each hook individually. We only use the hash of all
323 hooks because there is no other easy way to detect changes to local imports.
324
325 Returns:
326 True if this hook is approved to run; False otherwise.
327
328 Raises:
329 HookError: Raised if the user doesn't approve and abort_if_user_denies
330 was passed to the consturctor.
331 """
Doug Anderson37282b42011-03-04 11:54:18 -0800332 hooks_config = self._hooks_project.config
333 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
334
335 # Get the last hash that the user approved for this hook; may be None.
336 old_hash = hooks_config.GetString(git_approval_key)
337
338 # Get the current hash so we can tell if scripts changed since approval.
339 new_hash = self._GetHash()
340
341 if old_hash is not None:
342 # User previously approved hook and asked not to be prompted again.
343 if new_hash == old_hash:
344 # Approval matched. We're done.
345 return True
346 else:
347 # Give the user a reason why we're prompting, since they last told
348 # us to "never ask again".
349 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
350 self._hook_type)
351 else:
352 prompt = ''
353
354 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
355 if sys.stdout.isatty():
356 prompt += ('Repo %s run the script:\n'
357 ' %s\n'
358 '\n'
359 'Do you want to allow this script to run '
360 '(yes/yes-never-ask-again/NO)? ') % (
361 self._GetMustVerb(), self._script_fullpath)
362 response = raw_input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900363 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800364
365 # User is doing a one-time approval.
366 if response in ('y', 'yes'):
367 return True
368 elif response == 'yes-never-ask-again':
369 hooks_config.SetString(git_approval_key, new_hash)
370 return True
371
372 # For anything else, we'll assume no approval.
373 if self._abort_if_user_denies:
374 raise HookError('You must allow the %s hook or use --no-verify.' %
375 self._hook_type)
376
377 return False
378
379 def _ExecuteHook(self, **kwargs):
380 """Actually execute the given hook.
381
382 This will run the hook's 'main' function in our python interpreter.
383
384 Args:
385 kwargs: Keyword arguments to pass to the hook. These are often specific
386 to the hook type. For instance, pre-upload hooks will contain
387 a project_list.
388 """
389 # Keep sys.path and CWD stashed away so that we can always restore them
390 # upon function exit.
391 orig_path = os.getcwd()
392 orig_syspath = sys.path
393
394 try:
395 # Always run hooks with CWD as topdir.
396 os.chdir(self._topdir)
397
398 # Put the hook dir as the first item of sys.path so hooks can do
399 # relative imports. We want to replace the repo dir as [0] so
400 # hooks can't import repo files.
401 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
402
403 # Exec, storing global context in the context dict. We catch exceptions
404 # and convert to a HookError w/ just the failing traceback.
405 context = {}
406 try:
407 execfile(self._script_fullpath, context)
408 except Exception:
409 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
410 traceback.format_exc(), self._hook_type))
411
412 # Running the script should have defined a main() function.
413 if 'main' not in context:
414 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
415
416
417 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
418 # We don't actually want hooks to define their main with this argument--
419 # it's there to remind them that their hook should always take **kwargs.
420 # For instance, a pre-upload hook should be defined like:
421 # def main(project_list, **kwargs):
422 #
423 # This allows us to later expand the API without breaking old hooks.
424 kwargs = kwargs.copy()
425 kwargs['hook_should_take_kwargs'] = True
426
427 # Call the main function in the hook. If the hook should cause the
428 # build to fail, it will raise an Exception. We'll catch that convert
429 # to a HookError w/ just the failing traceback.
430 try:
431 context['main'](**kwargs)
432 except Exception:
433 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
434 'above.' % (
435 traceback.format_exc(), self._hook_type))
436 finally:
437 # Restore sys.path and CWD.
438 sys.path = orig_syspath
439 os.chdir(orig_path)
440
441 def Run(self, user_allows_all_hooks, **kwargs):
442 """Run the hook.
443
444 If the hook doesn't exist (because there is no hooks project or because
445 this particular hook is not enabled), this is a no-op.
446
447 Args:
448 user_allows_all_hooks: If True, we will never prompt about running the
449 hook--we'll just assume it's OK to run it.
450 kwargs: Keyword arguments to pass to the hook. These are often specific
451 to the hook type. For instance, pre-upload hooks will contain
452 a project_list.
453
454 Raises:
455 HookError: If there was a problem finding the hook or the user declined
456 to run a required hook (from _CheckForHookApproval).
457 """
458 # No-op if there is no hooks project or if hook is disabled.
459 if ((not self._hooks_project) or
460 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
461 return
462
463 # Bail with a nice error if we can't find the hook.
464 if not os.path.isfile(self._script_fullpath):
465 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
466
467 # Make sure the user is OK with running the hook.
468 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
469 return
470
471 # Run the hook with the same version of python we're using.
472 self._ExecuteHook(**kwargs)
473
474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475class Project(object):
476 def __init__(self,
477 manifest,
478 name,
479 remote,
480 gitdir,
481 worktree,
482 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700483 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800484 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700485 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700486 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700487 sync_c = False,
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -0700488 upstream = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700489 self.manifest = manifest
490 self.name = name
491 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800492 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800493 if worktree:
494 self.worktree = worktree.replace('\\', '/')
495 else:
496 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700497 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700498 self.revisionExpr = revisionExpr
499
500 if revisionId is None \
501 and revisionExpr \
502 and IsId(revisionExpr):
503 self.revisionId = revisionExpr
504 else:
505 self.revisionId = revisionId
506
Mike Pontillod3153822012-02-28 11:53:24 -0800507 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700508 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700509 self.sync_c = sync_c
Brian Harring14a66742012-09-28 20:21:57 -0700510 self.upstream = upstream
Mike Pontillod3153822012-02-28 11:53:24 -0800511
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500514 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.config = GitConfig.ForRepository(
516 gitdir = self.gitdir,
517 defaults = self.manifest.globalConfig)
518
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800519 if self.worktree:
520 self.work_git = self._GitGetByExec(self, bare=False)
521 else:
522 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700524 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525
Doug Anderson37282b42011-03-04 11:54:18 -0800526 # This will be filled in if a project is later identified to be the
527 # project containing repo hooks.
528 self.enabled_repo_hooks = []
529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 @property
531 def Exists(self):
532 return os.path.isdir(self.gitdir)
533
534 @property
535 def CurrentBranch(self):
536 """Obtain the name of the currently checked out branch.
537 The branch name omits the 'refs/heads/' prefix.
538 None is returned if the project is on a detached HEAD.
539 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700540 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 if b.startswith(R_HEADS):
542 return b[len(R_HEADS):]
543 return None
544
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700545 def IsRebaseInProgress(self):
546 w = self.worktree
547 g = os.path.join(w, '.git')
548 return os.path.exists(os.path.join(g, 'rebase-apply')) \
549 or os.path.exists(os.path.join(g, 'rebase-merge')) \
550 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 def IsDirty(self, consider_untracked=True):
553 """Is the working directory modified in some way?
554 """
555 self.work_git.update_index('-q',
556 '--unmerged',
557 '--ignore-missing',
558 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900559 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 return True
561 if self.work_git.DiffZ('diff-files'):
562 return True
563 if consider_untracked and self.work_git.LsOthers():
564 return True
565 return False
566
567 _userident_name = None
568 _userident_email = None
569
570 @property
571 def UserName(self):
572 """Obtain the user's personal name.
573 """
574 if self._userident_name is None:
575 self._LoadUserIdentity()
576 return self._userident_name
577
578 @property
579 def UserEmail(self):
580 """Obtain the user's email address. This is very likely
581 to be their Gerrit login.
582 """
583 if self._userident_email is None:
584 self._LoadUserIdentity()
585 return self._userident_email
586
587 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900588 u = self.bare_git.var('GIT_COMMITTER_IDENT')
589 m = re.compile("^(.*) <([^>]*)> ").match(u)
590 if m:
591 self._userident_name = m.group(1)
592 self._userident_email = m.group(2)
593 else:
594 self._userident_name = ''
595 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596
597 def GetRemote(self, name):
598 """Get the configuration for a single remote.
599 """
600 return self.config.GetRemote(name)
601
602 def GetBranch(self, name):
603 """Get the configuration for a single branch.
604 """
605 return self.config.GetBranch(name)
606
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700607 def GetBranches(self):
608 """Get all existing local branches.
609 """
610 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900611 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700612 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700613
David Pursehouse8a68ff92012-09-24 12:15:13 +0900614 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700615 if name.startswith(R_HEADS):
616 name = name[len(R_HEADS):]
617 b = self.GetBranch(name)
618 b.current = name == current
619 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900620 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700621 heads[name] = b
622
David Pursehouse8a68ff92012-09-24 12:15:13 +0900623 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700624 if name.startswith(R_PUB):
625 name = name[len(R_PUB):]
626 b = heads.get(name)
627 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900628 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700629
630 return heads
631
Colin Cross5acde752012-03-28 20:15:45 -0700632 def MatchesGroups(self, manifest_groups):
633 """Returns true if the manifest groups specified at init should cause
634 this project to be synced.
635 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700636 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700637
638 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700639 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700640 manifest_groups: "-group1,group2"
641 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700642 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700643 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
644 expanded_project_groups = ['all'] + (self.groups or [])
645
Conley Owens971de8e2012-04-16 10:36:08 -0700646 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700647 for group in expanded_manifest_groups:
648 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700649 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700650 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700651 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700652
Conley Owens971de8e2012-04-16 10:36:08 -0700653 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654
655## Status Display ##
656
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500657 def HasChanges(self):
658 """Returns true if there are uncommitted changes.
659 """
660 self.work_git.update_index('-q',
661 '--unmerged',
662 '--ignore-missing',
663 '--refresh')
664 if self.IsRebaseInProgress():
665 return True
666
667 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
668 return True
669
670 if self.work_git.DiffZ('diff-files'):
671 return True
672
673 if self.work_git.LsOthers():
674 return True
675
676 return False
677
Terence Haddock4655e812011-03-31 12:33:34 +0200678 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200680
681 Args:
682 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 """
684 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200685 if output_redir == None:
686 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700687 print(file=output_redir)
688 print('project %s/' % self.relpath, file=output_redir)
689 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 return
691
692 self.work_git.update_index('-q',
693 '--unmerged',
694 '--ignore-missing',
695 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700696 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
698 df = self.work_git.DiffZ('diff-files')
699 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100700 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700701 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
703 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200704 if not output_redir == None:
705 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 out.project('project %-40s', self.relpath + '/')
707
708 branch = self.CurrentBranch
709 if branch is None:
710 out.nobranch('(*** NO BRANCH ***)')
711 else:
712 out.branch('branch %s', branch)
713 out.nl()
714
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700715 if rb:
716 out.important('prior sync failed; rebase still in progress')
717 out.nl()
718
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 paths = list()
720 paths.extend(di.keys())
721 paths.extend(df.keys())
722 paths.extend(do)
723
724 paths = list(set(paths))
725 paths.sort()
726
727 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900728 try:
729 i = di[p]
730 except KeyError:
731 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900733 try:
734 f = df[p]
735 except KeyError:
736 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200737
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900738 if i:
739 i_status = i.status.upper()
740 else:
741 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900743 if f:
744 f_status = f.status.lower()
745 else:
746 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747
748 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800749 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750 i.src_path, p, i.level)
751 else:
752 line = ' %s%s\t%s' % (i_status, f_status, p)
753
754 if i and not f:
755 out.added('%s', line)
756 elif (i and f) or (not i and f):
757 out.changed('%s', line)
758 elif not i and not f:
759 out.untracked('%s', line)
760 else:
761 out.write('%s', line)
762 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200763
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700764 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765
pelyad67872d2012-03-28 14:49:58 +0300766 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 """Prints the status of the repository to stdout.
768 """
769 out = DiffColoring(self.config)
770 cmd = ['diff']
771 if out.is_on:
772 cmd.append('--color')
773 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300774 if absolute_paths:
775 cmd.append('--src-prefix=a/%s/' % self.relpath)
776 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 cmd.append('--')
778 p = GitCommand(self,
779 cmd,
780 capture_stdout = True,
781 capture_stderr = True)
782 has_diff = False
783 for line in p.process.stdout:
784 if not has_diff:
785 out.nl()
786 out.project('project %s/' % self.relpath)
787 out.nl()
788 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700789 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 p.Wait()
791
792
793## Publish / Upload ##
794
David Pursehouse8a68ff92012-09-24 12:15:13 +0900795 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700796 """Was the branch published (uploaded) for code review?
797 If so, returns the SHA-1 hash of the last published
798 state for the branch.
799 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700800 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900801 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700802 try:
803 return self.bare_git.rev_parse(key)
804 except GitError:
805 return None
806 else:
807 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900808 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700809 except KeyError:
810 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse8a68ff92012-09-24 12:15:13 +0900812 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 """Prunes any stale published refs.
814 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900815 if all_refs is None:
816 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 heads = set()
818 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900819 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 if name.startswith(R_HEADS):
821 heads.add(name)
822 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900823 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824
David Pursehouse8a68ff92012-09-24 12:15:13 +0900825 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826 n = name[len(R_PUB):]
827 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900828 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700830 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 """List any branches which can be uploaded for review.
832 """
833 heads = {}
834 pubed = {}
835
David Pursehouse8a68ff92012-09-24 12:15:13 +0900836 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900838 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900840 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841
842 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900843 for branch, ref_id in heads.iteritems():
844 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700846 if selected_branch and branch != selected_branch:
847 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800849 rb = self.GetUploadableBranch(branch)
850 if rb:
851 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 return ready
853
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800854 def GetUploadableBranch(self, branch_name):
855 """Get a single uploadable branch, or None.
856 """
857 branch = self.GetBranch(branch_name)
858 base = branch.LocalMerge
859 if branch.LocalMerge:
860 rb = ReviewableBranch(self, branch, base)
861 if rb.commits:
862 return rb
863 return None
864
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700865 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700866 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700867 auto_topic=False,
868 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 """Uploads the named branch for code review.
870 """
871 if branch is None:
872 branch = self.CurrentBranch
873 if branch is None:
874 raise GitError('not currently on a branch')
875
876 branch = self.GetBranch(branch)
877 if not branch.LocalMerge:
878 raise GitError('branch %s does not track a remote' % branch.name)
879 if not branch.remote.review:
880 raise GitError('remote %s has no review url' % branch.remote.name)
881
882 dest_branch = branch.merge
883 if not dest_branch.startswith(R_HEADS):
884 dest_branch = R_HEADS + dest_branch
885
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800886 if not branch.remote.projectname:
887 branch.remote.projectname = self.name
888 branch.remote.Save()
889
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800890 url = branch.remote.ReviewUrl(self.UserEmail)
891 if url is None:
892 raise UploadError('review not configured')
893 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800894
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800895 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800896 rp = ['gerrit receive-pack']
897 for e in people[0]:
898 rp.append('--reviewer=%s' % sq(e))
899 for e in people[1]:
900 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800901 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700902
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800903 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800904
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800905 if dest_branch.startswith(R_HEADS):
906 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700907
908 upload_type = 'for'
909 if draft:
910 upload_type = 'drafts'
911
912 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
913 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800914 if auto_topic:
915 ref_spec = ref_spec + '/' + branch.name
916 cmd.append(ref_spec)
917
918 if GitCommand(self, cmd, bare = True).Wait() != 0:
919 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
921 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
922 self.bare_git.UpdateRef(R_PUB + branch.name,
923 R_HEADS + branch.name,
924 message = msg)
925
926
927## Sync ##
928
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700929 def Sync_NetworkHalf(self,
930 quiet=False,
931 is_new=None,
932 current_branch_only=False,
933 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 """Perform only the network IO portion of the sync process.
935 Local working directory/branch state is not affected.
936 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700937 if is_new is None:
938 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200939 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 self._InitGitDir()
941 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700942
943 if is_new:
944 alt = os.path.join(self.gitdir, 'objects/info/alternates')
945 try:
946 fd = open(alt, 'rb')
947 try:
948 alt_dir = fd.readline().rstrip()
949 finally:
950 fd.close()
951 except IOError:
952 alt_dir = None
953 else:
954 alt_dir = None
955
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700956 if clone_bundle \
957 and alt_dir is None \
958 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700959 is_new = False
960
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700961 if not current_branch_only:
962 if self.sync_c:
963 current_branch_only = True
964 elif not self.manifest._loaded:
965 # Manifest cannot check defaults until it syncs.
966 current_branch_only = False
967 elif self.manifest.default.sync_c:
968 current_branch_only = True
969
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700970 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
971 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800973
974 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800975 self._InitMRef()
976 else:
977 self._InitMirrorHead()
978 try:
979 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
980 except OSError:
981 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800983
984 def PostRepoUpgrade(self):
985 self._InitHooks()
986
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900988 for copyfile in self.copyfiles:
989 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
David Pursehouse8a68ff92012-09-24 12:15:13 +0900991 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700992 if self.revisionId:
993 return self.revisionId
994
995 rem = self.GetRemote(self.remote.name)
996 rev = rem.ToLocal(self.revisionExpr)
997
David Pursehouse8a68ff92012-09-24 12:15:13 +0900998 if all_refs is not None and rev in all_refs:
999 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001000
1001 try:
1002 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1003 except GitError:
1004 raise ManifestInvalidRevisionError(
1005 'revision %s in %s not found' % (self.revisionExpr,
1006 self.name))
1007
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001008 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 """Perform only the local IO portion of the sync process.
1010 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001012 all_refs = self.bare_ref.all
1013 self.CleanPublishedCache(all_refs)
1014 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001015
David Pursehouse1d947b32012-10-25 12:23:11 +09001016 def _doff():
1017 self._FastForward(revid)
1018 self._CopyFiles()
1019
Skyler Kaufman835cd682011-03-08 12:14:41 -08001020 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001021 head = self.work_git.GetHead()
1022 if head.startswith(R_HEADS):
1023 branch = head[len(R_HEADS):]
1024 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001025 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001026 except KeyError:
1027 head = None
1028 else:
1029 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001031 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 # Currently on a detached HEAD. The user is assumed to
1033 # not have any local modifications worth worrying about.
1034 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001035 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001036 syncbuf.fail(self, _PriorSyncFailedError())
1037 return
1038
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001039 if head == revid:
1040 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001041 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001042 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001043 if not syncbuf.detach_head:
1044 return
1045 else:
1046 lost = self._revlist(not_rev(revid), HEAD)
1047 if lost:
1048 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001049
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001051 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001052 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 syncbuf.fail(self, e)
1054 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001058 if head == revid:
1059 # No changes; don't do anything further.
1060 #
1061 return
1062
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001065 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001067 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001069 syncbuf.info(self,
1070 "leaving %s; does not track upstream",
1071 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001073 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001074 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001075 syncbuf.fail(self, e)
1076 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001078 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001080 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001081 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084 if not_merged:
1085 if upstream_gain:
1086 # The user has published this branch and some of those
1087 # commits are not yet merged upstream. We do not want
1088 # to rewrite the published commits so we punt.
1089 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001090 syncbuf.fail(self,
1091 "branch %s is published (but not merged) and is now %d commits behind"
1092 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001093 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001094 elif pub == head:
1095 # All published commits are merged, and thus we are a
1096 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001097 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001098 syncbuf.later1(self, _doff)
1099 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001101 # Examine the local commits not in the remote. Find the
1102 # last one attributed to this user, if any.
1103 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001104 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 last_mine = None
1106 cnt_mine = 0
1107 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001108 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001109 if committer_email == self.UserEmail:
1110 last_mine = commit_id
1111 cnt_mine += 1
1112
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001113 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115
1116 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001117 syncbuf.fail(self, _DirtyError())
1118 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001120 # If the upstream switched on us, warn the user.
1121 #
1122 if branch.merge != self.revisionExpr:
1123 if branch.merge and self.revisionExpr:
1124 syncbuf.info(self,
1125 'manifest switched %s...%s',
1126 branch.merge,
1127 self.revisionExpr)
1128 elif branch.merge:
1129 syncbuf.info(self,
1130 'manifest no longer tracks %s',
1131 branch.merge)
1132
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001133 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001135 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001137 syncbuf.info(self,
1138 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001139 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001141 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001142 if not ID_RE.match(self.revisionExpr):
1143 # in case of manifest sync the revisionExpr might be a SHA1
1144 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001145 branch.Save()
1146
Mike Pontillod3153822012-02-28 11:53:24 -08001147 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001148 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001149 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001150 self._CopyFiles()
1151 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001152 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001154 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001155 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001156 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001157 syncbuf.fail(self, e)
1158 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001160 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001161
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001162 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 # dest should already be an absolute path, but src is project relative
1164 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001165 abssrc = os.path.join(self.worktree, src)
1166 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
James W. Mills24c13082012-04-12 15:04:13 -05001168 def AddAnnotation(self, name, value, keep):
1169 self.annotations.append(_Annotation(name, value, keep))
1170
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001171 def DownloadPatchSet(self, change_id, patch_id):
1172 """Download a single patch set of a single change to FETCH_HEAD.
1173 """
1174 remote = self.GetRemote(self.remote.name)
1175
1176 cmd = ['fetch', remote.name]
1177 cmd.append('refs/changes/%2.2d/%d/%d' \
1178 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001179 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001180 if GitCommand(self, cmd, bare=True).Wait() != 0:
1181 return None
1182 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001183 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001184 change_id,
1185 patch_id,
1186 self.bare_git.rev_parse('FETCH_HEAD'))
1187
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188
1189## Branch Management ##
1190
1191 def StartBranch(self, name):
1192 """Create a new branch off the manifest's revision.
1193 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001194 head = self.work_git.GetHead()
1195 if head == (R_HEADS + name):
1196 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001197
David Pursehouse8a68ff92012-09-24 12:15:13 +09001198 all_refs = self.bare_ref.all
1199 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001200 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001201 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001202 capture_stdout = True,
1203 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001204
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001205 branch = self.GetBranch(name)
1206 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001207 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001208 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001209
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001210 if head.startswith(R_HEADS):
1211 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001212 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001213 except KeyError:
1214 head = None
1215
1216 if revid and head and revid == head:
1217 ref = os.path.join(self.gitdir, R_HEADS + name)
1218 try:
1219 os.makedirs(os.path.dirname(ref))
1220 except OSError:
1221 pass
1222 _lwrite(ref, '%s\n' % revid)
1223 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1224 'ref: %s%s\n' % (R_HEADS, name))
1225 branch.Save()
1226 return True
1227
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001228 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001229 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001230 capture_stdout = True,
1231 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001232 branch.Save()
1233 return True
1234 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235
Wink Saville02d79452009-04-10 13:01:24 -07001236 def CheckoutBranch(self, name):
1237 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001238
1239 Args:
1240 name: The name of the branch to checkout.
1241
1242 Returns:
1243 True if the checkout succeeded; False if it didn't; None if the branch
1244 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001245 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001246 rev = R_HEADS + name
1247 head = self.work_git.GetHead()
1248 if head == rev:
1249 # Already on the branch
1250 #
1251 return True
Wink Saville02d79452009-04-10 13:01:24 -07001252
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001254 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001255 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001256 except KeyError:
1257 # Branch does not exist in this project
1258 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001259 return None
Wink Saville02d79452009-04-10 13:01:24 -07001260
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001261 if head.startswith(R_HEADS):
1262 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001263 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001264 except KeyError:
1265 head = None
1266
1267 if head == revid:
1268 # Same revision; just update HEAD to point to the new
1269 # target branch, but otherwise take no other action.
1270 #
1271 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1272 'ref: %s%s\n' % (R_HEADS, name))
1273 return True
1274
1275 return GitCommand(self,
1276 ['checkout', name, '--'],
1277 capture_stdout = True,
1278 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001279
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001280 def AbandonBranch(self, name):
1281 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001282
1283 Args:
1284 name: The name of the branch to abandon.
1285
1286 Returns:
1287 True if the abandon succeeded; False if it didn't; None if the branch
1288 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001289 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001290 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 all_refs = self.bare_ref.all
1292 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001293 # Doesn't exist
1294 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001295
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001296 head = self.work_git.GetHead()
1297 if head == rev:
1298 # We can't destroy the branch while we are sitting
1299 # on it. Switch to a detached HEAD.
1300 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001301 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001302
David Pursehouse8a68ff92012-09-24 12:15:13 +09001303 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001305 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1306 '%s\n' % revid)
1307 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001309
1310 return GitCommand(self,
1311 ['branch', '-D', name],
1312 capture_stdout = True,
1313 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001314
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 def PruneHeads(self):
1316 """Prune any topic branches already merged into upstream.
1317 """
1318 cb = self.CurrentBranch
1319 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001320 left = self._allrefs
1321 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 if name.startswith(R_HEADS):
1323 name = name[len(R_HEADS):]
1324 if cb is None or name != cb:
1325 kill.append(name)
1326
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001327 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 if cb is not None \
1329 and not self._revlist(HEAD + '...' + rev) \
1330 and not self.IsDirty(consider_untracked = False):
1331 self.work_git.DetachHead(HEAD)
1332 kill.append(cb)
1333
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001335 old = self.bare_git.GetHead()
1336 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1338
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 try:
1340 self.bare_git.DetachHead(rev)
1341
1342 b = ['branch', '-d']
1343 b.extend(kill)
1344 b = GitCommand(self, b, bare=True,
1345 capture_stdout=True,
1346 capture_stderr=True)
1347 b.Wait()
1348 finally:
1349 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001350 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001352 for branch in kill:
1353 if (R_HEADS + branch) not in left:
1354 self.CleanPublishedCache()
1355 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356
1357 if cb and cb not in kill:
1358 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001359 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
1361 kept = []
1362 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001363 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 branch = self.GetBranch(branch)
1365 base = branch.LocalMerge
1366 if not base:
1367 base = rev
1368 kept.append(ReviewableBranch(self, branch, base))
1369 return kept
1370
1371
1372## Direct Git Commands ##
1373
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001374 def _RemoteFetch(self, name=None,
1375 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001376 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001377 quiet=False,
1378 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001379
1380 is_sha1 = False
1381 tag_name = None
1382
Brian Harring14a66742012-09-28 20:21:57 -07001383 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001384 try:
1385 # if revision (sha or tag) is not present then following function
1386 # throws an error.
1387 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1388 return True
1389 except GitError:
1390 # There is no such persistent revision. We have to fetch it.
1391 return False
Brian Harring14a66742012-09-28 20:21:57 -07001392
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001393 if current_branch_only:
1394 if ID_RE.match(self.revisionExpr) is not None:
1395 is_sha1 = True
1396 elif self.revisionExpr.startswith(R_TAGS):
1397 # this is a tag and its sha1 value should never change
1398 tag_name = self.revisionExpr[len(R_TAGS):]
1399
1400 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001401 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001402 return True
Brian Harring14a66742012-09-28 20:21:57 -07001403 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1404 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406 if not name:
1407 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001408
1409 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001410 remote = self.GetRemote(name)
1411 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001412 ssh_proxy = True
1413
Shawn O. Pearce88443382010-10-08 10:02:09 +02001414 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001415 if alt_dir and 'objects' == os.path.basename(alt_dir):
1416 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001417 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1418 remote = self.GetRemote(name)
1419
David Pursehouse8a68ff92012-09-24 12:15:13 +09001420 all_refs = self.bare_ref.all
1421 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001422 tmp = set()
1423
David Pursehouse8a68ff92012-09-24 12:15:13 +09001424 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1425 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001426 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001427 all_refs[r] = ref_id
1428 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001429 continue
1430
David Pursehouse8a68ff92012-09-24 12:15:13 +09001431 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001432 continue
1433
David Pursehouse8a68ff92012-09-24 12:15:13 +09001434 r = 'refs/_alt/%s' % ref_id
1435 all_refs[r] = ref_id
1436 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001437 tmp.add(r)
1438
David Pursehouse8a68ff92012-09-24 12:15:13 +09001439 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001440 ref_names.sort()
1441
1442 tmp_packed = ''
1443 old_packed = ''
1444
1445 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001446 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001447 tmp_packed += line
1448 if r not in tmp:
1449 old_packed += line
1450
1451 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001452 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001453 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001454
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001455 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001456
1457 # The --depth option only affects the initial fetch; after that we'll do
1458 # full fetches of changes.
1459 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1460 if depth and initial:
1461 cmd.append('--depth=%s' % depth)
1462
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001463 if quiet:
1464 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001465 if not self.worktree:
1466 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001467 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001468
Brian Harring14a66742012-09-28 20:21:57 -07001469 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001470 # Fetch whole repo
1471 cmd.append('--tags')
1472 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1473 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001474 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001475 cmd.append(tag_name)
1476 else:
1477 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001478 if is_sha1:
1479 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001480 if branch.startswith(R_HEADS):
1481 branch = branch[len(R_HEADS):]
1482 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001483
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001484 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001485 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001486 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1487 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001488 ok = True
1489 break
Brian Harring14a66742012-09-28 20:21:57 -07001490 elif current_branch_only and is_sha1 and ret == 128:
1491 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1492 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1493 # abort the optimization attempt and do a full sync.
1494 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001495 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001496
1497 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001498 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001499 if old_packed != '':
1500 _lwrite(packed_refs, old_packed)
1501 else:
1502 os.remove(packed_refs)
1503 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001504
1505 if is_sha1 and current_branch_only and self.upstream:
1506 # We just synced the upstream given branch; verify we
1507 # got what we wanted, else trigger a second run of all
1508 # refs.
1509 if not CheckForSha1():
1510 return self._RemoteFetch(name=name, current_branch_only=False,
1511 initial=False, quiet=quiet, alt_dir=alt_dir)
1512
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001513 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001514
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001515 def _ApplyCloneBundle(self, initial=False, quiet=False):
1516 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1517 return False
1518
1519 remote = self.GetRemote(self.remote.name)
1520 bundle_url = remote.url + '/clone.bundle'
1521 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001522 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1523 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001524 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1525 return False
1526
1527 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1528 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1529
1530 exist_dst = os.path.exists(bundle_dst)
1531 exist_tmp = os.path.exists(bundle_tmp)
1532
1533 if not initial and not exist_dst and not exist_tmp:
1534 return False
1535
1536 if not exist_dst:
1537 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1538 if not exist_dst:
1539 return False
1540
1541 cmd = ['fetch']
1542 if quiet:
1543 cmd.append('--quiet')
1544 if not self.worktree:
1545 cmd.append('--update-head-ok')
1546 cmd.append(bundle_dst)
1547 for f in remote.fetch:
1548 cmd.append(str(f))
1549 cmd.append('refs/tags/*:refs/tags/*')
1550
1551 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001552 if os.path.exists(bundle_dst):
1553 os.remove(bundle_dst)
1554 if os.path.exists(bundle_tmp):
1555 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001556 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001557
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001558 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001559 if os.path.exists(dstPath):
1560 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001561
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001562 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001563 if quiet:
1564 cmd += ['--silent']
1565 if os.path.exists(tmpPath):
1566 size = os.stat(tmpPath).st_size
1567 if size >= 1024:
1568 cmd += ['--continue-at', '%d' % (size,)]
1569 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001570 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001571 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1572 cmd += ['--proxy', os.environ['http_proxy']]
1573 cmd += [srcUrl]
1574
1575 if IsTrace():
1576 Trace('%s', ' '.join(cmd))
1577 try:
1578 proc = subprocess.Popen(cmd)
1579 except OSError:
1580 return False
1581
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001582 curlret = proc.wait()
1583
1584 if curlret == 22:
1585 # From curl man page:
1586 # 22: HTTP page not retrieved. The requested url was not found or
1587 # returned another error with the HTTP error code being 400 or above.
1588 # This return code only appears if -f, --fail is used.
1589 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001590 print("Server does not provide clone.bundle; ignoring.",
1591 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001592 return False
1593
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001594 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001595 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001596 os.rename(tmpPath, dstPath)
1597 return True
1598 else:
1599 os.remove(tmpPath)
1600 return False
1601 else:
1602 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 def _Checkout(self, rev, quiet=False):
1605 cmd = ['checkout']
1606 if quiet:
1607 cmd.append('-q')
1608 cmd.append(rev)
1609 cmd.append('--')
1610 if GitCommand(self, cmd).Wait() != 0:
1611 if self._allrefs:
1612 raise GitError('%s checkout %s ' % (self.name, rev))
1613
Pierre Tardye5a21222011-03-24 16:28:18 +01001614 def _CherryPick(self, rev, quiet=False):
1615 cmd = ['cherry-pick']
1616 cmd.append(rev)
1617 cmd.append('--')
1618 if GitCommand(self, cmd).Wait() != 0:
1619 if self._allrefs:
1620 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1621
Erwan Mahea94f1622011-08-19 13:56:09 +02001622 def _Revert(self, rev, quiet=False):
1623 cmd = ['revert']
1624 cmd.append('--no-edit')
1625 cmd.append(rev)
1626 cmd.append('--')
1627 if GitCommand(self, cmd).Wait() != 0:
1628 if self._allrefs:
1629 raise GitError('%s revert %s ' % (self.name, rev))
1630
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001631 def _ResetHard(self, rev, quiet=True):
1632 cmd = ['reset', '--hard']
1633 if quiet:
1634 cmd.append('-q')
1635 cmd.append(rev)
1636 if GitCommand(self, cmd).Wait() != 0:
1637 raise GitError('%s reset --hard %s ' % (self.name, rev))
1638
1639 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001640 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 if onto is not None:
1642 cmd.extend(['--onto', onto])
1643 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001644 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645 raise GitError('%s rebase %s ' % (self.name, upstream))
1646
Pierre Tardy3d125942012-05-04 12:18:12 +02001647 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001649 if ffonly:
1650 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651 if GitCommand(self, cmd).Wait() != 0:
1652 raise GitError('%s merge %s ' % (self.name, head))
1653
1654 def _InitGitDir(self):
1655 if not os.path.exists(self.gitdir):
1656 os.makedirs(self.gitdir)
1657 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001658
Shawn O. Pearce88443382010-10-08 10:02:09 +02001659 mp = self.manifest.manifestProject
1660 ref_dir = mp.config.GetString('repo.reference')
1661
1662 if ref_dir:
1663 mirror_git = os.path.join(ref_dir, self.name + '.git')
1664 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1665 self.relpath + '.git')
1666
1667 if os.path.exists(mirror_git):
1668 ref_dir = mirror_git
1669
1670 elif os.path.exists(repo_git):
1671 ref_dir = repo_git
1672
1673 else:
1674 ref_dir = None
1675
1676 if ref_dir:
1677 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1678 os.path.join(ref_dir, 'objects') + '\n')
1679
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001680 if self.manifest.IsMirror:
1681 self.config.SetString('core.bare', 'true')
1682 else:
1683 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684
1685 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001686 try:
1687 to_rm = os.listdir(hooks)
1688 except OSError:
1689 to_rm = []
1690 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001692 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693
1694 m = self.manifest.manifestProject.config
1695 for key in ['user.name', 'user.email']:
1696 if m.Has(key, include_defaults = False):
1697 self.config.SetString(key, m.GetString(key))
1698
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001699 def _InitHooks(self):
1700 hooks = self._gitdir_path('hooks')
1701 if not os.path.exists(hooks):
1702 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001703 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001704 name = os.path.basename(stock_hook)
1705
Victor Boivie65e0f352011-04-18 11:23:29 +02001706 if name in ('commit-msg',) and not self.remote.review \
1707 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001708 # Don't install a Gerrit Code Review hook if this
1709 # project does not appear to use it for reviews.
1710 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001711 # Since the manifest project is one of those, but also
1712 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001713 continue
1714
1715 dst = os.path.join(hooks, name)
1716 if os.path.islink(dst):
1717 continue
1718 if os.path.exists(dst):
1719 if filecmp.cmp(stock_hook, dst, shallow=False):
1720 os.remove(dst)
1721 else:
1722 _error("%s: Not replacing %s hook", self.relpath, name)
1723 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001724 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001725 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001726 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001727 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001728 raise GitError('filesystem must support symlinks')
1729 else:
1730 raise
1731
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001733 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001735 remote.url = self.remote.url
1736 remote.review = self.remote.review
1737 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001739 if self.worktree:
1740 remote.ResetFetch(mirror=False)
1741 else:
1742 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 remote.Save()
1744
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745 def _InitMRef(self):
1746 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001747 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001749 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001750 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001751
1752 def _InitAnyMRef(self, ref):
1753 cur = self.bare_ref.symref(ref)
1754
1755 if self.revisionId:
1756 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1757 msg = 'manifest set to %s' % self.revisionId
1758 dst = self.revisionId + '^0'
1759 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1760 else:
1761 remote = self.GetRemote(self.remote.name)
1762 dst = remote.ToLocal(self.revisionExpr)
1763 if cur != dst:
1764 msg = 'manifest set to %s' % self.revisionExpr
1765 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 def _InitWorkTree(self):
1768 dotgit = os.path.join(self.worktree, '.git')
1769 if not os.path.exists(dotgit):
1770 os.makedirs(dotgit)
1771
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772 for name in ['config',
1773 'description',
1774 'hooks',
1775 'info',
1776 'logs',
1777 'objects',
1778 'packed-refs',
1779 'refs',
1780 'rr-cache',
1781 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001782 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001783 src = os.path.join(self.gitdir, name)
1784 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001785 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001786 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001787 else:
1788 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001789 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001790 if e.errno == errno.EPERM:
1791 raise GitError('filesystem must support symlinks')
1792 else:
1793 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001795 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796
1797 cmd = ['read-tree', '--reset', '-u']
1798 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001799 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800 if GitCommand(self, cmd).Wait() != 0:
1801 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001802
1803 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1804 if not os.path.exists(rr_cache):
1805 os.makedirs(rr_cache)
1806
Shawn O. Pearce93609662009-04-21 10:50:33 -07001807 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808
1809 def _gitdir_path(self, path):
1810 return os.path.join(self.gitdir, path)
1811
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001812 def _revlist(self, *args, **kw):
1813 a = []
1814 a.extend(args)
1815 a.append('--')
1816 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817
1818 @property
1819 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001820 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821
1822 class _GitGetByExec(object):
1823 def __init__(self, project, bare):
1824 self._project = project
1825 self._bare = bare
1826
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001827 def LsOthers(self):
1828 p = GitCommand(self._project,
1829 ['ls-files',
1830 '-z',
1831 '--others',
1832 '--exclude-standard'],
1833 bare = False,
1834 capture_stdout = True,
1835 capture_stderr = True)
1836 if p.Wait() == 0:
1837 out = p.stdout
1838 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09001839 return out[:-1].split('\0') # pylint: disable=W1401
1840 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841 return []
1842
1843 def DiffZ(self, name, *args):
1844 cmd = [name]
1845 cmd.append('-z')
1846 cmd.extend(args)
1847 p = GitCommand(self._project,
1848 cmd,
1849 bare = False,
1850 capture_stdout = True,
1851 capture_stderr = True)
1852 try:
1853 out = p.process.stdout.read()
1854 r = {}
1855 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09001856 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001857 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001858 try:
1859 info = out.next()
1860 path = out.next()
1861 except StopIteration:
1862 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863
1864 class _Info(object):
1865 def __init__(self, path, omode, nmode, oid, nid, state):
1866 self.path = path
1867 self.src_path = None
1868 self.old_mode = omode
1869 self.new_mode = nmode
1870 self.old_id = oid
1871 self.new_id = nid
1872
1873 if len(state) == 1:
1874 self.status = state
1875 self.level = None
1876 else:
1877 self.status = state[:1]
1878 self.level = state[1:]
1879 while self.level.startswith('0'):
1880 self.level = self.level[1:]
1881
1882 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001883 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001884 if info.status in ('R', 'C'):
1885 info.src_path = info.path
1886 info.path = out.next()
1887 r[info.path] = info
1888 return r
1889 finally:
1890 p.Wait()
1891
1892 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001893 if self._bare:
1894 path = os.path.join(self._project.gitdir, HEAD)
1895 else:
1896 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001897 fd = open(path, 'rb')
1898 try:
1899 line = fd.read()
1900 finally:
1901 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001902 if line.startswith('ref: '):
1903 return line[5:-1]
1904 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001905
1906 def SetHead(self, ref, message=None):
1907 cmdv = []
1908 if message is not None:
1909 cmdv.extend(['-m', message])
1910 cmdv.append(HEAD)
1911 cmdv.append(ref)
1912 self.symbolic_ref(*cmdv)
1913
1914 def DetachHead(self, new, message=None):
1915 cmdv = ['--no-deref']
1916 if message is not None:
1917 cmdv.extend(['-m', message])
1918 cmdv.append(HEAD)
1919 cmdv.append(new)
1920 self.update_ref(*cmdv)
1921
1922 def UpdateRef(self, name, new, old=None,
1923 message=None,
1924 detach=False):
1925 cmdv = []
1926 if message is not None:
1927 cmdv.extend(['-m', message])
1928 if detach:
1929 cmdv.append('--no-deref')
1930 cmdv.append(name)
1931 cmdv.append(new)
1932 if old is not None:
1933 cmdv.append(old)
1934 self.update_ref(*cmdv)
1935
1936 def DeleteRef(self, name, old=None):
1937 if not old:
1938 old = self.rev_parse(name)
1939 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001940 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001941
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001942 def rev_list(self, *args, **kw):
1943 if 'format' in kw:
1944 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1945 else:
1946 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001947 cmdv.extend(args)
1948 p = GitCommand(self._project,
1949 cmdv,
1950 bare = self._bare,
1951 capture_stdout = True,
1952 capture_stderr = True)
1953 r = []
1954 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001955 if line[-1] == '\n':
1956 line = line[:-1]
1957 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001958 if p.Wait() != 0:
1959 raise GitError('%s rev-list %s: %s' % (
1960 self._project.name,
1961 str(args),
1962 p.stderr))
1963 return r
1964
1965 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001966 """Allow arbitrary git commands using pythonic syntax.
1967
1968 This allows you to do things like:
1969 git_obj.rev_parse('HEAD')
1970
1971 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1972 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07001973 Any other positional arguments will be passed to the git command, and the
1974 following keyword arguments are supported:
1975 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08001976
1977 Args:
1978 name: The name of the git command to call. Any '_' characters will
1979 be replaced with '-'.
1980
1981 Returns:
1982 A callable object that will try to call git with the named command.
1983 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001984 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07001985 def runner(*args, **kwargs):
1986 cmdv = []
1987 config = kwargs.pop('config', None)
1988 for k in kwargs:
1989 raise TypeError('%s() got an unexpected keyword argument %r'
1990 % (name, k))
1991 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07001992 if not git_require((1, 7, 2)):
1993 raise ValueError('cannot set config on command line for %s()'
1994 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07001995 for k, v in config.iteritems():
1996 cmdv.append('-c')
1997 cmdv.append('%s=%s' % (k, v))
1998 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001999 cmdv.extend(args)
2000 p = GitCommand(self._project,
2001 cmdv,
2002 bare = self._bare,
2003 capture_stdout = True,
2004 capture_stderr = True)
2005 if p.Wait() != 0:
2006 raise GitError('%s %s: %s' % (
2007 self._project.name,
2008 name,
2009 p.stderr))
2010 r = p.stdout
2011 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2012 return r[:-1]
2013 return r
2014 return runner
2015
2016
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002017class _PriorSyncFailedError(Exception):
2018 def __str__(self):
2019 return 'prior sync failed; rebase still in progress'
2020
2021class _DirtyError(Exception):
2022 def __str__(self):
2023 return 'contains uncommitted changes'
2024
2025class _InfoMessage(object):
2026 def __init__(self, project, text):
2027 self.project = project
2028 self.text = text
2029
2030 def Print(self, syncbuf):
2031 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2032 syncbuf.out.nl()
2033
2034class _Failure(object):
2035 def __init__(self, project, why):
2036 self.project = project
2037 self.why = why
2038
2039 def Print(self, syncbuf):
2040 syncbuf.out.fail('error: %s/: %s',
2041 self.project.relpath,
2042 str(self.why))
2043 syncbuf.out.nl()
2044
2045class _Later(object):
2046 def __init__(self, project, action):
2047 self.project = project
2048 self.action = action
2049
2050 def Run(self, syncbuf):
2051 out = syncbuf.out
2052 out.project('project %s/', self.project.relpath)
2053 out.nl()
2054 try:
2055 self.action()
2056 out.nl()
2057 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002058 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002059 out.nl()
2060 return False
2061
2062class _SyncColoring(Coloring):
2063 def __init__(self, config):
2064 Coloring.__init__(self, config, 'reposync')
2065 self.project = self.printer('header', attr = 'bold')
2066 self.info = self.printer('info')
2067 self.fail = self.printer('fail', fg='red')
2068
2069class SyncBuffer(object):
2070 def __init__(self, config, detach_head=False):
2071 self._messages = []
2072 self._failures = []
2073 self._later_queue1 = []
2074 self._later_queue2 = []
2075
2076 self.out = _SyncColoring(config)
2077 self.out.redirect(sys.stderr)
2078
2079 self.detach_head = detach_head
2080 self.clean = True
2081
2082 def info(self, project, fmt, *args):
2083 self._messages.append(_InfoMessage(project, fmt % args))
2084
2085 def fail(self, project, err=None):
2086 self._failures.append(_Failure(project, err))
2087 self.clean = False
2088
2089 def later1(self, project, what):
2090 self._later_queue1.append(_Later(project, what))
2091
2092 def later2(self, project, what):
2093 self._later_queue2.append(_Later(project, what))
2094
2095 def Finish(self):
2096 self._PrintMessages()
2097 self._RunLater()
2098 self._PrintMessages()
2099 return self.clean
2100
2101 def _RunLater(self):
2102 for q in ['_later_queue1', '_later_queue2']:
2103 if not self._RunQueue(q):
2104 return
2105
2106 def _RunQueue(self, queue):
2107 for m in getattr(self, queue):
2108 if not m.Run(self):
2109 self.clean = False
2110 return False
2111 setattr(self, queue, [])
2112 return True
2113
2114 def _PrintMessages(self):
2115 for m in self._messages:
2116 m.Print(self)
2117 for m in self._failures:
2118 m.Print(self)
2119
2120 self._messages = []
2121 self._failures = []
2122
2123
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124class MetaProject(Project):
2125 """A special project housed under .repo.
2126 """
2127 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128 Project.__init__(self,
2129 manifest = manifest,
2130 name = name,
2131 gitdir = gitdir,
2132 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002133 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002135 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002136 revisionId = None,
2137 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138
2139 def PreSync(self):
2140 if self.Exists:
2141 cb = self.CurrentBranch
2142 if cb:
2143 base = self.GetBranch(cb).merge
2144 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002145 self.revisionExpr = base
2146 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147
Florian Vallee5d016502012-06-07 17:19:26 +02002148 def MetaBranchSwitch(self, target):
2149 """ Prepare MetaProject for manifest branch switch
2150 """
2151
2152 # detach and delete manifest branch, allowing a new
2153 # branch to take over
2154 syncbuf = SyncBuffer(self.config, detach_head = True)
2155 self.Sync_LocalHalf(syncbuf)
2156 syncbuf.Finish()
2157
2158 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002159 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002160 capture_stdout = True,
2161 capture_stderr = True).Wait() == 0
2162
2163
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002164 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002165 def LastFetch(self):
2166 try:
2167 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2168 return os.path.getmtime(fh)
2169 except OSError:
2170 return 0
2171
2172 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002173 def HasChanges(self):
2174 """Has the remote received new commits not yet checked out?
2175 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002176 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002177 return False
2178
David Pursehouse8a68ff92012-09-24 12:15:13 +09002179 all_refs = self.bare_ref.all
2180 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002181 head = self.work_git.GetHead()
2182 if head.startswith(R_HEADS):
2183 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002184 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002185 except KeyError:
2186 head = None
2187
2188 if revid == head:
2189 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002190 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002191 return True
2192 return False