blob: 3f0fd80bf73a7ca017be00bcc6bb5db1526bc6e1 [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
Doug Anderson37282b42011-03-04 11:54:18 -080015import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
18import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
23import sys
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070024import time
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import urllib2
26
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070027try:
28 import threading as _threading
29except ImportError:
30 import dummy_threading as _threading
31
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070032try:
33 from os import SEEK_END
34except ImportError:
35 SEEK_END = 2
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037from color import Coloring
38from git_command import GitCommand
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070039from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070040from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080041from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080042from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070043from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070047_urllib_lock = _threading.Lock()
48
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070049def _lwrite(path, content):
50 lock = '%s.lock' % path
51
52 fd = open(lock, 'wb')
53 try:
54 fd.write(content)
55 finally:
56 fd.close()
57
58 try:
59 os.rename(lock, path)
60 except OSError:
61 os.remove(lock)
62 raise
63
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
66 print >>sys.stderr, 'error: %s' % msg
67
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068def not_rev(r):
69 return '^' + r
70
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080071def sq(r):
72 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080073
Doug Anderson8ced8642011-01-10 14:16:30 -080074_project_hook_list = None
75def _ProjectHooks():
76 """List the hooks present in the 'hooks' directory.
77
78 These hooks are project hooks and are copied to the '.git/hooks' directory
79 of all subprojects.
80
81 This function caches the list of hooks (based on the contents of the
82 'repo/hooks' directory) on the first call.
83
84 Returns:
85 A list of absolute paths to all of the files in the hooks directory.
86 """
87 global _project_hook_list
88 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089 d = os.path.abspath(os.path.dirname(__file__))
90 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080091 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
92 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080093
94def relpath(dst, src):
95 src = os.path.dirname(src)
96 top = os.path.commonprefix([dst, src])
97 if top.endswith('/'):
98 top = top[:-1]
99 else:
100 top = os.path.dirname(top)
101
102 tmp = src
103 rel = ''
104 while top != tmp:
105 rel += '../'
106 tmp = os.path.dirname(tmp)
107 return rel + dst[len(top) + 1:]
108
109
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700110class DownloadedChange(object):
111 _commit_cache = None
112
113 def __init__(self, project, base, change_id, ps_id, commit):
114 self.project = project
115 self.base = base
116 self.change_id = change_id
117 self.ps_id = ps_id
118 self.commit = commit
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 self.commit,
131 '--')
132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
150 self._commit_cache = self.project.bare_git.rev_list(
151 '--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
164 for commit in self.project.bare_git.rev_list(
165 not_rev(self.base),
166 R_HEADS + self.name,
167 '--'):
168 r[commit[0:8]] = commit
169 return r
170
171 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 def date(self):
173 return self.project.bare_git.log(
174 '--pretty=format:%cd',
175 '-n', '1',
176 R_HEADS + self.name,
177 '--')
178
Brian Harring435370c2012-07-28 15:37:04 -0700179 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
Brian Harring435370c2012-07-28 15:37:04 -0700182 auto_topic=auto_topic,
183 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700185 def GetPublishedRefs(self):
186 refs = {}
187 output = self.project.bare_git.ls_remote(
188 self.branch.remote.SshReviewUrl(self.project.UserEmail),
189 'refs/changes/*')
190 for line in output.split('\n'):
191 try:
192 (sha, ref) = line.split()
193 refs[sha] = ref
194 except ValueError:
195 pass
196
197 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198
199class StatusColoring(Coloring):
200 def __init__(self, config):
201 Coloring.__init__(self, config, 'status')
202 self.project = self.printer('header', attr = 'bold')
203 self.branch = self.printer('header', attr = 'bold')
204 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700205 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
207 self.added = self.printer('added', fg = 'green')
208 self.changed = self.printer('changed', fg = 'red')
209 self.untracked = self.printer('untracked', fg = 'red')
210
211
212class DiffColoring(Coloring):
213 def __init__(self, config):
214 Coloring.__init__(self, config, 'diff')
215 self.project = self.printer('header', attr = 'bold')
216
James W. Mills24c13082012-04-12 15:04:13 -0500217class _Annotation:
218 def __init__(self, name, value, keep):
219 self.name = name
220 self.value = value
221 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
223class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800224 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225 self.src = src
226 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800227 self.abs_src = abssrc
228 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229
230 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800231 src = self.abs_src
232 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233 # copy file if it does not exist or is out of date
234 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
235 try:
236 # remove existing file first, since it might be read-only
237 if os.path.exists(dest):
238 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400239 else:
240 dir = os.path.dirname(dest)
241 if not os.path.isdir(dir):
242 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 shutil.copy(src, dest)
244 # make the file read-only
245 mode = os.stat(dest)[stat.ST_MODE]
246 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
247 os.chmod(dest, mode)
248 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700249 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700250
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700251class RemoteSpec(object):
252 def __init__(self,
253 name,
254 url = None,
255 review = None):
256 self.name = name
257 self.url = url
258 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Doug Anderson37282b42011-03-04 11:54:18 -0800260class RepoHook(object):
261 """A RepoHook contains information about a script to run as a hook.
262
263 Hooks are used to run a python script before running an upload (for instance,
264 to run presubmit checks). Eventually, we may have hooks for other actions.
265
266 This shouldn't be confused with files in the 'repo/hooks' directory. Those
267 files are copied into each '.git/hooks' folder for each project. Repo-level
268 hooks are associated instead with repo actions.
269
270 Hooks are always python. When a hook is run, we will load the hook into the
271 interpreter and execute its main() function.
272 """
273 def __init__(self,
274 hook_type,
275 hooks_project,
276 topdir,
277 abort_if_user_denies=False):
278 """RepoHook constructor.
279
280 Params:
281 hook_type: A string representing the type of hook. This is also used
282 to figure out the name of the file containing the hook. For
283 example: 'pre-upload'.
284 hooks_project: The project containing the repo hooks. If you have a
285 manifest, this is manifest.repo_hooks_project. OK if this is None,
286 which will make the hook a no-op.
287 topdir: Repo's top directory (the one containing the .repo directory).
288 Scripts will run with CWD as this directory. If you have a manifest,
289 this is manifest.topdir
290 abort_if_user_denies: If True, we'll throw a HookError() if the user
291 doesn't allow us to run the hook.
292 """
293 self._hook_type = hook_type
294 self._hooks_project = hooks_project
295 self._topdir = topdir
296 self._abort_if_user_denies = abort_if_user_denies
297
298 # Store the full path to the script for convenience.
299 if self._hooks_project:
300 self._script_fullpath = os.path.join(self._hooks_project.worktree,
301 self._hook_type + '.py')
302 else:
303 self._script_fullpath = None
304
305 def _GetHash(self):
306 """Return a hash of the contents of the hooks directory.
307
308 We'll just use git to do this. This hash has the property that if anything
309 changes in the directory we will return a different has.
310
311 SECURITY CONSIDERATION:
312 This hash only represents the contents of files in the hook directory, not
313 any other files imported or called by hooks. Changes to imported files
314 can change the script behavior without affecting the hash.
315
316 Returns:
317 A string representing the hash. This will always be ASCII so that it can
318 be printed to the user easily.
319 """
320 assert self._hooks_project, "Must have hooks to calculate their hash."
321
322 # We will use the work_git object rather than just calling GetRevisionId().
323 # That gives us a hash of the latest checked in version of the files that
324 # the user will actually be executing. Specifically, GetRevisionId()
325 # doesn't appear to change even if a user checks out a different version
326 # of the hooks repo (via git checkout) nor if a user commits their own revs.
327 #
328 # NOTE: Local (non-committed) changes will not be factored into this hash.
329 # I think this is OK, since we're really only worried about warning the user
330 # about upstream changes.
331 return self._hooks_project.work_git.rev_parse('HEAD')
332
333 def _GetMustVerb(self):
334 """Return 'must' if the hook is required; 'should' if not."""
335 if self._abort_if_user_denies:
336 return 'must'
337 else:
338 return 'should'
339
340 def _CheckForHookApproval(self):
341 """Check to see whether this hook has been approved.
342
343 We'll look at the hash of all of the hooks. If this matches the hash that
344 the user last approved, we're done. If it doesn't, we'll ask the user
345 about approval.
346
347 Note that we ask permission for each individual hook even though we use
348 the hash of all hooks when detecting changes. We'd like the user to be
349 able to approve / deny each hook individually. We only use the hash of all
350 hooks because there is no other easy way to detect changes to local imports.
351
352 Returns:
353 True if this hook is approved to run; False otherwise.
354
355 Raises:
356 HookError: Raised if the user doesn't approve and abort_if_user_denies
357 was passed to the consturctor.
358 """
359 hooks_dir = self._hooks_project.worktree
360 hooks_config = self._hooks_project.config
361 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
362
363 # Get the last hash that the user approved for this hook; may be None.
364 old_hash = hooks_config.GetString(git_approval_key)
365
366 # Get the current hash so we can tell if scripts changed since approval.
367 new_hash = self._GetHash()
368
369 if old_hash is not None:
370 # User previously approved hook and asked not to be prompted again.
371 if new_hash == old_hash:
372 # Approval matched. We're done.
373 return True
374 else:
375 # Give the user a reason why we're prompting, since they last told
376 # us to "never ask again".
377 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
378 self._hook_type)
379 else:
380 prompt = ''
381
382 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
383 if sys.stdout.isatty():
384 prompt += ('Repo %s run the script:\n'
385 ' %s\n'
386 '\n'
387 'Do you want to allow this script to run '
388 '(yes/yes-never-ask-again/NO)? ') % (
389 self._GetMustVerb(), self._script_fullpath)
390 response = raw_input(prompt).lower()
391 print
392
393 # User is doing a one-time approval.
394 if response in ('y', 'yes'):
395 return True
396 elif response == 'yes-never-ask-again':
397 hooks_config.SetString(git_approval_key, new_hash)
398 return True
399
400 # For anything else, we'll assume no approval.
401 if self._abort_if_user_denies:
402 raise HookError('You must allow the %s hook or use --no-verify.' %
403 self._hook_type)
404
405 return False
406
407 def _ExecuteHook(self, **kwargs):
408 """Actually execute the given hook.
409
410 This will run the hook's 'main' function in our python interpreter.
411
412 Args:
413 kwargs: Keyword arguments to pass to the hook. These are often specific
414 to the hook type. For instance, pre-upload hooks will contain
415 a project_list.
416 """
417 # Keep sys.path and CWD stashed away so that we can always restore them
418 # upon function exit.
419 orig_path = os.getcwd()
420 orig_syspath = sys.path
421
422 try:
423 # Always run hooks with CWD as topdir.
424 os.chdir(self._topdir)
425
426 # Put the hook dir as the first item of sys.path so hooks can do
427 # relative imports. We want to replace the repo dir as [0] so
428 # hooks can't import repo files.
429 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
430
431 # Exec, storing global context in the context dict. We catch exceptions
432 # and convert to a HookError w/ just the failing traceback.
433 context = {}
434 try:
435 execfile(self._script_fullpath, context)
436 except Exception:
437 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
438 traceback.format_exc(), self._hook_type))
439
440 # Running the script should have defined a main() function.
441 if 'main' not in context:
442 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
443
444
445 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
446 # We don't actually want hooks to define their main with this argument--
447 # it's there to remind them that their hook should always take **kwargs.
448 # For instance, a pre-upload hook should be defined like:
449 # def main(project_list, **kwargs):
450 #
451 # This allows us to later expand the API without breaking old hooks.
452 kwargs = kwargs.copy()
453 kwargs['hook_should_take_kwargs'] = True
454
455 # Call the main function in the hook. If the hook should cause the
456 # build to fail, it will raise an Exception. We'll catch that convert
457 # to a HookError w/ just the failing traceback.
458 try:
459 context['main'](**kwargs)
460 except Exception:
461 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
462 'above.' % (
463 traceback.format_exc(), self._hook_type))
464 finally:
465 # Restore sys.path and CWD.
466 sys.path = orig_syspath
467 os.chdir(orig_path)
468
469 def Run(self, user_allows_all_hooks, **kwargs):
470 """Run the hook.
471
472 If the hook doesn't exist (because there is no hooks project or because
473 this particular hook is not enabled), this is a no-op.
474
475 Args:
476 user_allows_all_hooks: If True, we will never prompt about running the
477 hook--we'll just assume it's OK to run it.
478 kwargs: Keyword arguments to pass to the hook. These are often specific
479 to the hook type. For instance, pre-upload hooks will contain
480 a project_list.
481
482 Raises:
483 HookError: If there was a problem finding the hook or the user declined
484 to run a required hook (from _CheckForHookApproval).
485 """
486 # No-op if there is no hooks project or if hook is disabled.
487 if ((not self._hooks_project) or
488 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
489 return
490
491 # Bail with a nice error if we can't find the hook.
492 if not os.path.isfile(self._script_fullpath):
493 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
494
495 # Make sure the user is OK with running the hook.
496 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
497 return
498
499 # Run the hook with the same version of python we're using.
500 self._ExecuteHook(**kwargs)
501
502
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700503class Project(object):
504 def __init__(self,
505 manifest,
506 name,
507 remote,
508 gitdir,
509 worktree,
510 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700511 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800512 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700513 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700514 groups = None,
515 sync_c = False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 self.manifest = manifest
517 self.name = name
518 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800519 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800520 if worktree:
521 self.worktree = worktree.replace('\\', '/')
522 else:
523 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700524 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700525 self.revisionExpr = revisionExpr
526
527 if revisionId is None \
528 and revisionExpr \
529 and IsId(revisionExpr):
530 self.revisionId = revisionExpr
531 else:
532 self.revisionId = revisionId
533
Mike Pontillod3153822012-02-28 11:53:24 -0800534 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700535 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700536 self.sync_c = sync_c
Mike Pontillod3153822012-02-28 11:53:24 -0800537
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500540 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 self.config = GitConfig.ForRepository(
542 gitdir = self.gitdir,
543 defaults = self.manifest.globalConfig)
544
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800545 if self.worktree:
546 self.work_git = self._GitGetByExec(self, bare=False)
547 else:
548 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700550 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551
Doug Anderson37282b42011-03-04 11:54:18 -0800552 # This will be filled in if a project is later identified to be the
553 # project containing repo hooks.
554 self.enabled_repo_hooks = []
555
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 @property
557 def Exists(self):
558 return os.path.isdir(self.gitdir)
559
560 @property
561 def CurrentBranch(self):
562 """Obtain the name of the currently checked out branch.
563 The branch name omits the 'refs/heads/' prefix.
564 None is returned if the project is on a detached HEAD.
565 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700566 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 if b.startswith(R_HEADS):
568 return b[len(R_HEADS):]
569 return None
570
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700571 def IsRebaseInProgress(self):
572 w = self.worktree
573 g = os.path.join(w, '.git')
574 return os.path.exists(os.path.join(g, 'rebase-apply')) \
575 or os.path.exists(os.path.join(g, 'rebase-merge')) \
576 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700578 def IsDirty(self, consider_untracked=True):
579 """Is the working directory modified in some way?
580 """
581 self.work_git.update_index('-q',
582 '--unmerged',
583 '--ignore-missing',
584 '--refresh')
585 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
586 return True
587 if self.work_git.DiffZ('diff-files'):
588 return True
589 if consider_untracked and self.work_git.LsOthers():
590 return True
591 return False
592
593 _userident_name = None
594 _userident_email = None
595
596 @property
597 def UserName(self):
598 """Obtain the user's personal name.
599 """
600 if self._userident_name is None:
601 self._LoadUserIdentity()
602 return self._userident_name
603
604 @property
605 def UserEmail(self):
606 """Obtain the user's email address. This is very likely
607 to be their Gerrit login.
608 """
609 if self._userident_email is None:
610 self._LoadUserIdentity()
611 return self._userident_email
612
613 def _LoadUserIdentity(self):
614 u = self.bare_git.var('GIT_COMMITTER_IDENT')
615 m = re.compile("^(.*) <([^>]*)> ").match(u)
616 if m:
617 self._userident_name = m.group(1)
618 self._userident_email = m.group(2)
619 else:
620 self._userident_name = ''
621 self._userident_email = ''
622
623 def GetRemote(self, name):
624 """Get the configuration for a single remote.
625 """
626 return self.config.GetRemote(name)
627
628 def GetBranch(self, name):
629 """Get the configuration for a single branch.
630 """
631 return self.config.GetBranch(name)
632
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700633 def GetBranches(self):
634 """Get all existing local branches.
635 """
636 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700637 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700638 heads = {}
639 pubd = {}
640
641 for name, id in all.iteritems():
642 if name.startswith(R_HEADS):
643 name = name[len(R_HEADS):]
644 b = self.GetBranch(name)
645 b.current = name == current
646 b.published = None
647 b.revision = id
648 heads[name] = b
649
650 for name, id in all.iteritems():
651 if name.startswith(R_PUB):
652 name = name[len(R_PUB):]
653 b = heads.get(name)
654 if b:
655 b.published = id
656
657 return heads
658
Colin Cross5acde752012-03-28 20:15:45 -0700659 def MatchesGroups(self, manifest_groups):
660 """Returns true if the manifest groups specified at init should cause
661 this project to be synced.
662 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700663 All projects are implicitly labelled with "default".
Colin Cross5acde752012-03-28 20:15:45 -0700664
Conley Owens971de8e2012-04-16 10:36:08 -0700665 labels are resolved in order. In the example case of
666 project_groups: "default,group1,group2"
667 manifest_groups: "-group1,group2"
668 the project will be matched.
669 """
Colin Crosseca119e2012-05-24 15:39:14 -0700670 if self.groups is None:
671 return True
Conley Owens971de8e2012-04-16 10:36:08 -0700672 matched = False
673 for group in manifest_groups:
674 if group.startswith('-') and group[1:] in self.groups:
675 matched = False
676 elif group in self.groups:
677 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700678
Conley Owens971de8e2012-04-16 10:36:08 -0700679 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680
681## Status Display ##
682
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500683 def HasChanges(self):
684 """Returns true if there are uncommitted changes.
685 """
686 self.work_git.update_index('-q',
687 '--unmerged',
688 '--ignore-missing',
689 '--refresh')
690 if self.IsRebaseInProgress():
691 return True
692
693 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
694 return True
695
696 if self.work_git.DiffZ('diff-files'):
697 return True
698
699 if self.work_git.LsOthers():
700 return True
701
702 return False
703
Terence Haddock4655e812011-03-31 12:33:34 +0200704 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200706
707 Args:
708 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700709 """
710 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200711 if output_redir == None:
712 output_redir = sys.stdout
713 print >>output_redir, ''
714 print >>output_redir, 'project %s/' % self.relpath
715 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 return
717
718 self.work_git.update_index('-q',
719 '--unmerged',
720 '--ignore-missing',
721 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700722 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
724 df = self.work_git.DiffZ('diff-files')
725 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100726 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700727 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
729 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200730 if not output_redir == None:
731 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732 out.project('project %-40s', self.relpath + '/')
733
734 branch = self.CurrentBranch
735 if branch is None:
736 out.nobranch('(*** NO BRANCH ***)')
737 else:
738 out.branch('branch %s', branch)
739 out.nl()
740
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700741 if rb:
742 out.important('prior sync failed; rebase still in progress')
743 out.nl()
744
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 paths = list()
746 paths.extend(di.keys())
747 paths.extend(df.keys())
748 paths.extend(do)
749
750 paths = list(set(paths))
751 paths.sort()
752
753 for p in paths:
754 try: i = di[p]
755 except KeyError: i = None
756
757 try: f = df[p]
758 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200759
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760 if i: i_status = i.status.upper()
761 else: i_status = '-'
762
763 if f: f_status = f.status.lower()
764 else: f_status = '-'
765
766 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800767 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 i.src_path, p, i.level)
769 else:
770 line = ' %s%s\t%s' % (i_status, f_status, p)
771
772 if i and not f:
773 out.added('%s', line)
774 elif (i and f) or (not i and f):
775 out.changed('%s', line)
776 elif not i and not f:
777 out.untracked('%s', line)
778 else:
779 out.write('%s', line)
780 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200781
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700782 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783
pelyad67872d2012-03-28 14:49:58 +0300784 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785 """Prints the status of the repository to stdout.
786 """
787 out = DiffColoring(self.config)
788 cmd = ['diff']
789 if out.is_on:
790 cmd.append('--color')
791 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300792 if absolute_paths:
793 cmd.append('--src-prefix=a/%s/' % self.relpath)
794 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 cmd.append('--')
796 p = GitCommand(self,
797 cmd,
798 capture_stdout = True,
799 capture_stderr = True)
800 has_diff = False
801 for line in p.process.stdout:
802 if not has_diff:
803 out.nl()
804 out.project('project %s/' % self.relpath)
805 out.nl()
806 has_diff = True
807 print line[:-1]
808 p.Wait()
809
810
811## Publish / Upload ##
812
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700813 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 """Was the branch published (uploaded) for code review?
815 If so, returns the SHA-1 hash of the last published
816 state for the branch.
817 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700818 key = R_PUB + branch
819 if all is None:
820 try:
821 return self.bare_git.rev_parse(key)
822 except GitError:
823 return None
824 else:
825 try:
826 return all[key]
827 except KeyError:
828 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700830 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 """Prunes any stale published refs.
832 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700833 if all is None:
834 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 heads = set()
836 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700837 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 if name.startswith(R_HEADS):
839 heads.add(name)
840 elif name.startswith(R_PUB):
841 canrm[name] = id
842
843 for name, id in canrm.iteritems():
844 n = name[len(R_PUB):]
845 if R_HEADS + n not in heads:
846 self.bare_git.DeleteRef(name, id)
847
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700848 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 """List any branches which can be uploaded for review.
850 """
851 heads = {}
852 pubed = {}
853
854 for name, id in self._allrefs.iteritems():
855 if name.startswith(R_HEADS):
856 heads[name[len(R_HEADS):]] = id
857 elif name.startswith(R_PUB):
858 pubed[name[len(R_PUB):]] = id
859
860 ready = []
861 for branch, id in heads.iteritems():
862 if branch in pubed and pubed[branch] == id:
863 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700864 if selected_branch and branch != selected_branch:
865 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800867 rb = self.GetUploadableBranch(branch)
868 if rb:
869 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 return ready
871
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800872 def GetUploadableBranch(self, branch_name):
873 """Get a single uploadable branch, or None.
874 """
875 branch = self.GetBranch(branch_name)
876 base = branch.LocalMerge
877 if branch.LocalMerge:
878 rb = ReviewableBranch(self, branch, base)
879 if rb.commits:
880 return rb
881 return None
882
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700883 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700884 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700885 auto_topic=False,
886 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887 """Uploads the named branch for code review.
888 """
889 if branch is None:
890 branch = self.CurrentBranch
891 if branch is None:
892 raise GitError('not currently on a branch')
893
894 branch = self.GetBranch(branch)
895 if not branch.LocalMerge:
896 raise GitError('branch %s does not track a remote' % branch.name)
897 if not branch.remote.review:
898 raise GitError('remote %s has no review url' % branch.remote.name)
899
900 dest_branch = branch.merge
901 if not dest_branch.startswith(R_HEADS):
902 dest_branch = R_HEADS + dest_branch
903
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800904 if not branch.remote.projectname:
905 branch.remote.projectname = self.name
906 branch.remote.Save()
907
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800908 url = branch.remote.ReviewUrl(self.UserEmail)
909 if url is None:
910 raise UploadError('review not configured')
911 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800912
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800913 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800914 rp = ['gerrit receive-pack']
915 for e in people[0]:
916 rp.append('--reviewer=%s' % sq(e))
917 for e in people[1]:
918 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800919 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700920
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800921 cmd.append(url)
922
923 if dest_branch.startswith(R_HEADS):
924 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700925
926 upload_type = 'for'
927 if draft:
928 upload_type = 'drafts'
929
930 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
931 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800932 if auto_topic:
933 ref_spec = ref_spec + '/' + branch.name
934 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800935
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800936 if GitCommand(self, cmd, bare = True).Wait() != 0:
937 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938
939 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
940 self.bare_git.UpdateRef(R_PUB + branch.name,
941 R_HEADS + branch.name,
942 message = msg)
943
944
945## Sync ##
946
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700947 def Sync_NetworkHalf(self,
948 quiet=False,
949 is_new=None,
950 current_branch_only=False,
951 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 """Perform only the network IO portion of the sync process.
953 Local working directory/branch state is not affected.
954 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700955 if is_new is None:
956 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200957 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 self._InitGitDir()
959 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700960
961 if is_new:
962 alt = os.path.join(self.gitdir, 'objects/info/alternates')
963 try:
964 fd = open(alt, 'rb')
965 try:
966 alt_dir = fd.readline().rstrip()
967 finally:
968 fd.close()
969 except IOError:
970 alt_dir = None
971 else:
972 alt_dir = None
973
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700974 if clone_bundle \
975 and alt_dir is None \
976 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700977 is_new = False
978
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700979 if not current_branch_only:
980 if self.sync_c:
981 current_branch_only = True
982 elif not self.manifest._loaded:
983 # Manifest cannot check defaults until it syncs.
984 current_branch_only = False
985 elif self.manifest.default.sync_c:
986 current_branch_only = True
987
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700988 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
989 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800991
992 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800993 self._InitMRef()
994 else:
995 self._InitMirrorHead()
996 try:
997 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
998 except OSError:
999 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001001
1002 def PostRepoUpgrade(self):
1003 self._InitHooks()
1004
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 def _CopyFiles(self):
1006 for file in self.copyfiles:
1007 file._Copy()
1008
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001009 def GetRevisionId(self, all=None):
1010 if self.revisionId:
1011 return self.revisionId
1012
1013 rem = self.GetRemote(self.remote.name)
1014 rev = rem.ToLocal(self.revisionExpr)
1015
1016 if all is not None and rev in all:
1017 return all[rev]
1018
1019 try:
1020 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1021 except GitError:
1022 raise ManifestInvalidRevisionError(
1023 'revision %s in %s not found' % (self.revisionExpr,
1024 self.name))
1025
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001026 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 """Perform only the local IO portion of the sync process.
1028 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001030 all = self.bare_ref.all
1031 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001032 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001033
1034 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001035 head = self.work_git.GetHead()
1036 if head.startswith(R_HEADS):
1037 branch = head[len(R_HEADS):]
1038 try:
1039 head = all[head]
1040 except KeyError:
1041 head = None
1042 else:
1043 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001045 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001046 # Currently on a detached HEAD. The user is assumed to
1047 # not have any local modifications worth worrying about.
1048 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001049 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001050 syncbuf.fail(self, _PriorSyncFailedError())
1051 return
1052
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001053 if head == revid:
1054 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001055 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001056 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001057 if not syncbuf.detach_head:
1058 return
1059 else:
1060 lost = self._revlist(not_rev(revid), HEAD)
1061 if lost:
1062 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001065 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001066 except GitError, e:
1067 syncbuf.fail(self, e)
1068 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001070 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001072 if head == revid:
1073 # No changes; don't do anything further.
1074 #
1075 return
1076
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001079 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001081 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001083 syncbuf.info(self,
1084 "leaving %s; does not track upstream",
1085 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001087 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 except GitError, e:
1089 syncbuf.fail(self, e)
1090 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001092 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001094 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001095 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001097 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098 if not_merged:
1099 if upstream_gain:
1100 # The user has published this branch and some of those
1101 # commits are not yet merged upstream. We do not want
1102 # to rewrite the published commits so we punt.
1103 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001104 syncbuf.fail(self,
1105 "branch %s is published (but not merged) and is now %d commits behind"
1106 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001108 elif pub == head:
1109 # All published commits are merged, and thus we are a
1110 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001111 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001112 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 self._CopyFiles()
1115 syncbuf.later1(self, _doff)
1116 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001118 # Examine the local commits not in the remote. Find the
1119 # last one attributed to this user, if any.
1120 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001121 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001122 last_mine = None
1123 cnt_mine = 0
1124 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001125 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001126 if committer_email == self.UserEmail:
1127 last_mine = commit_id
1128 cnt_mine += 1
1129
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001130 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001131 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132
1133 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001134 syncbuf.fail(self, _DirtyError())
1135 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001137 # If the upstream switched on us, warn the user.
1138 #
1139 if branch.merge != self.revisionExpr:
1140 if branch.merge and self.revisionExpr:
1141 syncbuf.info(self,
1142 'manifest switched %s...%s',
1143 branch.merge,
1144 self.revisionExpr)
1145 elif branch.merge:
1146 syncbuf.info(self,
1147 'manifest no longer tracks %s',
1148 branch.merge)
1149
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001150 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001152 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 syncbuf.info(self,
1155 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001156 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001158 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001159 if not ID_RE.match(self.revisionExpr):
1160 # in case of manifest sync the revisionExpr might be a SHA1
1161 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 branch.Save()
1163
Mike Pontillod3153822012-02-28 11:53:24 -08001164 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001165 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001166 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001167 self._CopyFiles()
1168 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001169 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001171 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001172 self._CopyFiles()
1173 except GitError, e:
1174 syncbuf.fail(self, e)
1175 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001177 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001178 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001179 self._CopyFiles()
1180 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001182 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183 # dest should already be an absolute path, but src is project relative
1184 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001185 abssrc = os.path.join(self.worktree, src)
1186 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
James W. Mills24c13082012-04-12 15:04:13 -05001188 def AddAnnotation(self, name, value, keep):
1189 self.annotations.append(_Annotation(name, value, keep))
1190
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001191 def DownloadPatchSet(self, change_id, patch_id):
1192 """Download a single patch set of a single change to FETCH_HEAD.
1193 """
1194 remote = self.GetRemote(self.remote.name)
1195
1196 cmd = ['fetch', remote.name]
1197 cmd.append('refs/changes/%2.2d/%d/%d' \
1198 % (change_id % 100, change_id, patch_id))
1199 cmd.extend(map(lambda x: str(x), remote.fetch))
1200 if GitCommand(self, cmd, bare=True).Wait() != 0:
1201 return None
1202 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001203 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001204 change_id,
1205 patch_id,
1206 self.bare_git.rev_parse('FETCH_HEAD'))
1207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208
1209## Branch Management ##
1210
1211 def StartBranch(self, name):
1212 """Create a new branch off the manifest's revision.
1213 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001214 head = self.work_git.GetHead()
1215 if head == (R_HEADS + name):
1216 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001218 all = self.bare_ref.all
1219 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001220 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001221 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001222 capture_stdout = True,
1223 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001224
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001225 branch = self.GetBranch(name)
1226 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001227 branch.merge = self.revisionExpr
1228 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001229
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001230 if head.startswith(R_HEADS):
1231 try:
1232 head = all[head]
1233 except KeyError:
1234 head = None
1235
1236 if revid and head and revid == head:
1237 ref = os.path.join(self.gitdir, R_HEADS + name)
1238 try:
1239 os.makedirs(os.path.dirname(ref))
1240 except OSError:
1241 pass
1242 _lwrite(ref, '%s\n' % revid)
1243 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1244 'ref: %s%s\n' % (R_HEADS, name))
1245 branch.Save()
1246 return True
1247
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001248 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001249 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001250 capture_stdout = True,
1251 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001252 branch.Save()
1253 return True
1254 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255
Wink Saville02d79452009-04-10 13:01:24 -07001256 def CheckoutBranch(self, name):
1257 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001258
1259 Args:
1260 name: The name of the branch to checkout.
1261
1262 Returns:
1263 True if the checkout succeeded; False if it didn't; None if the branch
1264 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001265 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001266 rev = R_HEADS + name
1267 head = self.work_git.GetHead()
1268 if head == rev:
1269 # Already on the branch
1270 #
1271 return True
Wink Saville02d79452009-04-10 13:01:24 -07001272
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001273 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001274 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001275 revid = all[rev]
1276 except KeyError:
1277 # Branch does not exist in this project
1278 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001279 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001280
1281 if head.startswith(R_HEADS):
1282 try:
1283 head = all[head]
1284 except KeyError:
1285 head = None
1286
1287 if head == revid:
1288 # Same revision; just update HEAD to point to the new
1289 # target branch, but otherwise take no other action.
1290 #
1291 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1292 'ref: %s%s\n' % (R_HEADS, name))
1293 return True
Wink Saville02d79452009-04-10 13:01:24 -07001294
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001295 return GitCommand(self,
1296 ['checkout', name, '--'],
1297 capture_stdout = True,
1298 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001299
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001300 def AbandonBranch(self, name):
1301 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001302
1303 Args:
1304 name: The name of the branch to abandon.
1305
1306 Returns:
1307 True if the abandon succeeded; False if it didn't; None if the branch
1308 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001309 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001310 rev = R_HEADS + name
1311 all = self.bare_ref.all
1312 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001313 # Doesn't exist
1314 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001315
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001316 head = self.work_git.GetHead()
1317 if head == rev:
1318 # We can't destroy the branch while we are sitting
1319 # on it. Switch to a detached HEAD.
1320 #
1321 head = all[head]
1322
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001323 revid = self.GetRevisionId(all)
1324 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001325 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1326 '%s\n' % revid)
1327 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001328 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001329
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001330 return GitCommand(self,
1331 ['branch', '-D', name],
1332 capture_stdout = True,
1333 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001334
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 def PruneHeads(self):
1336 """Prune any topic branches already merged into upstream.
1337 """
1338 cb = self.CurrentBranch
1339 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001340 left = self._allrefs
1341 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 if name.startswith(R_HEADS):
1343 name = name[len(R_HEADS):]
1344 if cb is None or name != cb:
1345 kill.append(name)
1346
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001347 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348 if cb is not None \
1349 and not self._revlist(HEAD + '...' + rev) \
1350 and not self.IsDirty(consider_untracked = False):
1351 self.work_git.DetachHead(HEAD)
1352 kill.append(cb)
1353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001355 old = self.bare_git.GetHead()
1356 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1358
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 try:
1360 self.bare_git.DetachHead(rev)
1361
1362 b = ['branch', '-d']
1363 b.extend(kill)
1364 b = GitCommand(self, b, bare=True,
1365 capture_stdout=True,
1366 capture_stderr=True)
1367 b.Wait()
1368 finally:
1369 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001370 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001372 for branch in kill:
1373 if (R_HEADS + branch) not in left:
1374 self.CleanPublishedCache()
1375 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376
1377 if cb and cb not in kill:
1378 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001379 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380
1381 kept = []
1382 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001383 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 branch = self.GetBranch(branch)
1385 base = branch.LocalMerge
1386 if not base:
1387 base = rev
1388 kept.append(ReviewableBranch(self, branch, base))
1389 return kept
1390
1391
1392## Direct Git Commands ##
1393
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001394 def _RemoteFetch(self, name=None,
1395 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001396 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001397 quiet=False,
1398 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001399
1400 is_sha1 = False
1401 tag_name = None
1402
1403 if current_branch_only:
1404 if ID_RE.match(self.revisionExpr) is not None:
1405 is_sha1 = True
1406 elif self.revisionExpr.startswith(R_TAGS):
1407 # this is a tag and its sha1 value should never change
1408 tag_name = self.revisionExpr[len(R_TAGS):]
1409
1410 if is_sha1 or tag_name is not None:
1411 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001412 # if revision (sha or tag) is not present then following function
1413 # throws an error.
1414 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001415 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001416 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001417 # There is no such persistent revision. We have to fetch it.
1418 pass
1419
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420 if not name:
1421 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001422
1423 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001424 remote = self.GetRemote(name)
1425 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001426 ssh_proxy = True
1427
Shawn O. Pearce88443382010-10-08 10:02:09 +02001428 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001429 if alt_dir and 'objects' == os.path.basename(alt_dir):
1430 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001431 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1432 remote = self.GetRemote(name)
1433
1434 all = self.bare_ref.all
1435 ids = set(all.values())
1436 tmp = set()
1437
1438 for r, id in GitRefs(ref_dir).all.iteritems():
1439 if r not in all:
1440 if r.startswith(R_TAGS) or remote.WritesTo(r):
1441 all[r] = id
1442 ids.add(id)
1443 continue
1444
1445 if id in ids:
1446 continue
1447
1448 r = 'refs/_alt/%s' % id
1449 all[r] = id
1450 ids.add(id)
1451 tmp.add(r)
1452
1453 ref_names = list(all.keys())
1454 ref_names.sort()
1455
1456 tmp_packed = ''
1457 old_packed = ''
1458
1459 for r in ref_names:
1460 line = '%s %s\n' % (all[r], r)
1461 tmp_packed += line
1462 if r not in tmp:
1463 old_packed += line
1464
1465 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001466 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001467 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001468
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001469 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001470
1471 # The --depth option only affects the initial fetch; after that we'll do
1472 # full fetches of changes.
1473 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1474 if depth and initial:
1475 cmd.append('--depth=%s' % depth)
1476
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001477 if quiet:
1478 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001479 if not self.worktree:
1480 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001481 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001482
1483 if not current_branch_only or is_sha1:
1484 # Fetch whole repo
1485 cmd.append('--tags')
1486 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1487 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001488 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001489 cmd.append(tag_name)
1490 else:
1491 branch = self.revisionExpr
1492 if branch.startswith(R_HEADS):
1493 branch = branch[len(R_HEADS):]
1494 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001495
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001496 ok = False
1497 for i in range(2):
1498 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1499 ok = True
1500 break
1501 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001502
1503 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001504 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001505 if old_packed != '':
1506 _lwrite(packed_refs, old_packed)
1507 else:
1508 os.remove(packed_refs)
1509 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001510 return ok
1511
1512 def _ApplyCloneBundle(self, initial=False, quiet=False):
1513 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1514 return False
1515
1516 remote = self.GetRemote(self.remote.name)
1517 bundle_url = remote.url + '/clone.bundle'
1518 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001519 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1520 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001521 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1522 return False
1523
1524 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1525 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001526
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001527 exist_dst = os.path.exists(bundle_dst)
1528 exist_tmp = os.path.exists(bundle_tmp)
1529
1530 if not initial and not exist_dst and not exist_tmp:
1531 return False
1532
1533 if not exist_dst:
1534 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1535 if not exist_dst:
1536 return False
1537
1538 cmd = ['fetch']
1539 if quiet:
1540 cmd.append('--quiet')
1541 if not self.worktree:
1542 cmd.append('--update-head-ok')
1543 cmd.append(bundle_dst)
1544 for f in remote.fetch:
1545 cmd.append(str(f))
1546 cmd.append('refs/tags/*:refs/tags/*')
1547
1548 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001549 if os.path.exists(bundle_dst):
1550 os.remove(bundle_dst)
1551 if os.path.exists(bundle_tmp):
1552 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001553 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001555 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001556 keep = True
1557 done = False
1558 dest = open(tmpPath, 'a+b')
1559 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001560 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001561 pos = dest.tell()
1562
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001563 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001564 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001565 req = urllib2.Request(srcUrl)
1566 if pos > 0:
Shawn O. Pearce98305532012-08-01 17:41:26 -07001567 req.add_header('Range', 'bytes=%d-' % (pos,))
Shawn O. Pearce29472462011-10-11 09:24:07 -07001568
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001569 try:
1570 r = urllib2.urlopen(req)
1571 except urllib2.HTTPError, e:
1572 def _content_type():
1573 try:
1574 return e.info()['content-type']
1575 except:
1576 return None
1577
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001578 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001579 keep = False
1580 return False
1581 elif _content_type() == 'text/plain':
1582 try:
1583 msg = e.read()
1584 if len(msg) > 0 and msg[-1] == '\n':
1585 msg = msg[0:-1]
Shawn O. Pearce98305532012-08-01 17:41:26 -07001586 msg = ' (%s)' % (msg,)
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001587 except:
1588 msg = ''
1589 else:
1590 try:
1591 from BaseHTTPServer import BaseHTTPRequestHandler
1592 res = BaseHTTPRequestHandler.responses[e.code]
1593 msg = ' (%s: %s)' % (res[0], res[1])
1594 except:
1595 msg = ''
1596 raise DownloadError('HTTP %s%s' % (e.code, msg))
1597 except urllib2.URLError, e:
1598 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1599 finally:
1600 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001601
1602 p = None
1603 try:
Shawn O. Pearcee0904f72012-08-01 20:44:23 -07001604 size = pos + int(r.headers.get('content-length', 0))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001605 unit = 1 << 10
1606
1607 if size and not quiet:
1608 if size > 1024 * 1.3:
1609 unit = 1 << 20
1610 desc = 'MB'
1611 else:
1612 desc = 'KB'
1613 p = Progress(
Shawn O. Pearce98305532012-08-01 17:41:26 -07001614 'Downloading %s' % (self.relpath,),
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001615 int(size) / unit,
1616 units=desc)
1617 if pos > 0:
1618 p.update(pos / unit)
1619
1620 s = 0
1621 while True:
1622 d = r.read(8192)
1623 if d == '':
1624 done = True
1625 return True
1626 dest.write(d)
1627 if p:
1628 s += len(d)
1629 if s >= unit:
1630 p.update(s / unit)
1631 s = s % unit
1632 if p:
1633 if s >= unit:
1634 p.update(s / unit)
1635 else:
1636 p.update(1)
1637 finally:
1638 r.close()
1639 if p:
1640 p.end()
1641 finally:
1642 dest.close()
1643
1644 if os.path.exists(dstPath):
1645 os.remove(dstPath)
1646 if done:
1647 os.rename(tmpPath, dstPath)
1648 elif not keep:
1649 os.remove(tmpPath)
1650
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651 def _Checkout(self, rev, quiet=False):
1652 cmd = ['checkout']
1653 if quiet:
1654 cmd.append('-q')
1655 cmd.append(rev)
1656 cmd.append('--')
1657 if GitCommand(self, cmd).Wait() != 0:
1658 if self._allrefs:
1659 raise GitError('%s checkout %s ' % (self.name, rev))
1660
Pierre Tardye5a21222011-03-24 16:28:18 +01001661 def _CherryPick(self, rev, quiet=False):
1662 cmd = ['cherry-pick']
1663 cmd.append(rev)
1664 cmd.append('--')
1665 if GitCommand(self, cmd).Wait() != 0:
1666 if self._allrefs:
1667 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1668
Erwan Mahea94f1622011-08-19 13:56:09 +02001669 def _Revert(self, rev, quiet=False):
1670 cmd = ['revert']
1671 cmd.append('--no-edit')
1672 cmd.append(rev)
1673 cmd.append('--')
1674 if GitCommand(self, cmd).Wait() != 0:
1675 if self._allrefs:
1676 raise GitError('%s revert %s ' % (self.name, rev))
1677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 def _ResetHard(self, rev, quiet=True):
1679 cmd = ['reset', '--hard']
1680 if quiet:
1681 cmd.append('-q')
1682 cmd.append(rev)
1683 if GitCommand(self, cmd).Wait() != 0:
1684 raise GitError('%s reset --hard %s ' % (self.name, rev))
1685
1686 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001687 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688 if onto is not None:
1689 cmd.extend(['--onto', onto])
1690 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001691 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 raise GitError('%s rebase %s ' % (self.name, upstream))
1693
Pierre Tardy3d125942012-05-04 12:18:12 +02001694 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001696 if ffonly:
1697 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698 if GitCommand(self, cmd).Wait() != 0:
1699 raise GitError('%s merge %s ' % (self.name, head))
1700
1701 def _InitGitDir(self):
1702 if not os.path.exists(self.gitdir):
1703 os.makedirs(self.gitdir)
1704 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001705
Shawn O. Pearce88443382010-10-08 10:02:09 +02001706 mp = self.manifest.manifestProject
1707 ref_dir = mp.config.GetString('repo.reference')
1708
1709 if ref_dir:
1710 mirror_git = os.path.join(ref_dir, self.name + '.git')
1711 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1712 self.relpath + '.git')
1713
1714 if os.path.exists(mirror_git):
1715 ref_dir = mirror_git
1716
1717 elif os.path.exists(repo_git):
1718 ref_dir = repo_git
1719
1720 else:
1721 ref_dir = None
1722
1723 if ref_dir:
1724 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1725 os.path.join(ref_dir, 'objects') + '\n')
1726
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001727 if self.manifest.IsMirror:
1728 self.config.SetString('core.bare', 'true')
1729 else:
1730 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731
1732 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001733 try:
1734 to_rm = os.listdir(hooks)
1735 except OSError:
1736 to_rm = []
1737 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001739 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001740
1741 m = self.manifest.manifestProject.config
1742 for key in ['user.name', 'user.email']:
1743 if m.Has(key, include_defaults = False):
1744 self.config.SetString(key, m.GetString(key))
1745
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001746 def _InitHooks(self):
1747 hooks = self._gitdir_path('hooks')
1748 if not os.path.exists(hooks):
1749 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001750 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001751 name = os.path.basename(stock_hook)
1752
Victor Boivie65e0f352011-04-18 11:23:29 +02001753 if name in ('commit-msg',) and not self.remote.review \
1754 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001755 # Don't install a Gerrit Code Review hook if this
1756 # project does not appear to use it for reviews.
1757 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001758 # Since the manifest project is one of those, but also
1759 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001760 continue
1761
1762 dst = os.path.join(hooks, name)
1763 if os.path.islink(dst):
1764 continue
1765 if os.path.exists(dst):
1766 if filecmp.cmp(stock_hook, dst, shallow=False):
1767 os.remove(dst)
1768 else:
1769 _error("%s: Not replacing %s hook", self.relpath, name)
1770 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001771 try:
1772 os.symlink(relpath(stock_hook, dst), dst)
1773 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001774 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001775 raise GitError('filesystem must support symlinks')
1776 else:
1777 raise
1778
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001779 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001780 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001782 remote.url = self.remote.url
1783 remote.review = self.remote.review
1784 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001786 if self.worktree:
1787 remote.ResetFetch(mirror=False)
1788 else:
1789 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790 remote.Save()
1791
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792 def _InitMRef(self):
1793 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001794 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001795
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001796 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001797 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001798
1799 def _InitAnyMRef(self, ref):
1800 cur = self.bare_ref.symref(ref)
1801
1802 if self.revisionId:
1803 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1804 msg = 'manifest set to %s' % self.revisionId
1805 dst = self.revisionId + '^0'
1806 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1807 else:
1808 remote = self.GetRemote(self.remote.name)
1809 dst = remote.ToLocal(self.revisionExpr)
1810 if cur != dst:
1811 msg = 'manifest set to %s' % self.revisionExpr
1812 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001813
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001814 def _InitWorkTree(self):
1815 dotgit = os.path.join(self.worktree, '.git')
1816 if not os.path.exists(dotgit):
1817 os.makedirs(dotgit)
1818
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001819 for name in ['config',
1820 'description',
1821 'hooks',
1822 'info',
1823 'logs',
1824 'objects',
1825 'packed-refs',
1826 'refs',
1827 'rr-cache',
1828 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001829 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001830 src = os.path.join(self.gitdir, name)
1831 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001832 if os.path.islink(dst) or not os.path.exists(dst):
1833 os.symlink(relpath(src, dst), dst)
1834 else:
1835 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001836 except OSError, e:
1837 if e.errno == errno.EPERM:
1838 raise GitError('filesystem must support symlinks')
1839 else:
1840 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001842 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001843
1844 cmd = ['read-tree', '--reset', '-u']
1845 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001846 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001847 if GitCommand(self, cmd).Wait() != 0:
1848 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001849
1850 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1851 if not os.path.exists(rr_cache):
1852 os.makedirs(rr_cache)
1853
Shawn O. Pearce93609662009-04-21 10:50:33 -07001854 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855
1856 def _gitdir_path(self, path):
1857 return os.path.join(self.gitdir, path)
1858
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001859 def _revlist(self, *args, **kw):
1860 a = []
1861 a.extend(args)
1862 a.append('--')
1863 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001864
1865 @property
1866 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001867 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001868
1869 class _GitGetByExec(object):
1870 def __init__(self, project, bare):
1871 self._project = project
1872 self._bare = bare
1873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001874 def LsOthers(self):
1875 p = GitCommand(self._project,
1876 ['ls-files',
1877 '-z',
1878 '--others',
1879 '--exclude-standard'],
1880 bare = False,
1881 capture_stdout = True,
1882 capture_stderr = True)
1883 if p.Wait() == 0:
1884 out = p.stdout
1885 if out:
1886 return out[:-1].split("\0")
1887 return []
1888
1889 def DiffZ(self, name, *args):
1890 cmd = [name]
1891 cmd.append('-z')
1892 cmd.extend(args)
1893 p = GitCommand(self._project,
1894 cmd,
1895 bare = False,
1896 capture_stdout = True,
1897 capture_stderr = True)
1898 try:
1899 out = p.process.stdout.read()
1900 r = {}
1901 if out:
1902 out = iter(out[:-1].split('\0'))
1903 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001904 try:
1905 info = out.next()
1906 path = out.next()
1907 except StopIteration:
1908 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001909
1910 class _Info(object):
1911 def __init__(self, path, omode, nmode, oid, nid, state):
1912 self.path = path
1913 self.src_path = None
1914 self.old_mode = omode
1915 self.new_mode = nmode
1916 self.old_id = oid
1917 self.new_id = nid
1918
1919 if len(state) == 1:
1920 self.status = state
1921 self.level = None
1922 else:
1923 self.status = state[:1]
1924 self.level = state[1:]
1925 while self.level.startswith('0'):
1926 self.level = self.level[1:]
1927
1928 info = info[1:].split(' ')
1929 info =_Info(path, *info)
1930 if info.status in ('R', 'C'):
1931 info.src_path = info.path
1932 info.path = out.next()
1933 r[info.path] = info
1934 return r
1935 finally:
1936 p.Wait()
1937
1938 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001939 if self._bare:
1940 path = os.path.join(self._project.gitdir, HEAD)
1941 else:
1942 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001943 fd = open(path, 'rb')
1944 try:
1945 line = fd.read()
1946 finally:
1947 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001948 if line.startswith('ref: '):
1949 return line[5:-1]
1950 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001951
1952 def SetHead(self, ref, message=None):
1953 cmdv = []
1954 if message is not None:
1955 cmdv.extend(['-m', message])
1956 cmdv.append(HEAD)
1957 cmdv.append(ref)
1958 self.symbolic_ref(*cmdv)
1959
1960 def DetachHead(self, new, message=None):
1961 cmdv = ['--no-deref']
1962 if message is not None:
1963 cmdv.extend(['-m', message])
1964 cmdv.append(HEAD)
1965 cmdv.append(new)
1966 self.update_ref(*cmdv)
1967
1968 def UpdateRef(self, name, new, old=None,
1969 message=None,
1970 detach=False):
1971 cmdv = []
1972 if message is not None:
1973 cmdv.extend(['-m', message])
1974 if detach:
1975 cmdv.append('--no-deref')
1976 cmdv.append(name)
1977 cmdv.append(new)
1978 if old is not None:
1979 cmdv.append(old)
1980 self.update_ref(*cmdv)
1981
1982 def DeleteRef(self, name, old=None):
1983 if not old:
1984 old = self.rev_parse(name)
1985 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001986 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001987
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001988 def rev_list(self, *args, **kw):
1989 if 'format' in kw:
1990 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1991 else:
1992 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993 cmdv.extend(args)
1994 p = GitCommand(self._project,
1995 cmdv,
1996 bare = self._bare,
1997 capture_stdout = True,
1998 capture_stderr = True)
1999 r = []
2000 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002001 if line[-1] == '\n':
2002 line = line[:-1]
2003 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002004 if p.Wait() != 0:
2005 raise GitError('%s rev-list %s: %s' % (
2006 self._project.name,
2007 str(args),
2008 p.stderr))
2009 return r
2010
2011 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002012 """Allow arbitrary git commands using pythonic syntax.
2013
2014 This allows you to do things like:
2015 git_obj.rev_parse('HEAD')
2016
2017 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2018 run. We'll replace the '_' with a '-' and try to run a git command.
2019 Any other arguments will be passed to the git command.
2020
2021 Args:
2022 name: The name of the git command to call. Any '_' characters will
2023 be replaced with '-'.
2024
2025 Returns:
2026 A callable object that will try to call git with the named command.
2027 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002028 name = name.replace('_', '-')
2029 def runner(*args):
2030 cmdv = [name]
2031 cmdv.extend(args)
2032 p = GitCommand(self._project,
2033 cmdv,
2034 bare = self._bare,
2035 capture_stdout = True,
2036 capture_stderr = True)
2037 if p.Wait() != 0:
2038 raise GitError('%s %s: %s' % (
2039 self._project.name,
2040 name,
2041 p.stderr))
2042 r = p.stdout
2043 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2044 return r[:-1]
2045 return r
2046 return runner
2047
2048
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002049class _PriorSyncFailedError(Exception):
2050 def __str__(self):
2051 return 'prior sync failed; rebase still in progress'
2052
2053class _DirtyError(Exception):
2054 def __str__(self):
2055 return 'contains uncommitted changes'
2056
2057class _InfoMessage(object):
2058 def __init__(self, project, text):
2059 self.project = project
2060 self.text = text
2061
2062 def Print(self, syncbuf):
2063 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2064 syncbuf.out.nl()
2065
2066class _Failure(object):
2067 def __init__(self, project, why):
2068 self.project = project
2069 self.why = why
2070
2071 def Print(self, syncbuf):
2072 syncbuf.out.fail('error: %s/: %s',
2073 self.project.relpath,
2074 str(self.why))
2075 syncbuf.out.nl()
2076
2077class _Later(object):
2078 def __init__(self, project, action):
2079 self.project = project
2080 self.action = action
2081
2082 def Run(self, syncbuf):
2083 out = syncbuf.out
2084 out.project('project %s/', self.project.relpath)
2085 out.nl()
2086 try:
2087 self.action()
2088 out.nl()
2089 return True
2090 except GitError, e:
2091 out.nl()
2092 return False
2093
2094class _SyncColoring(Coloring):
2095 def __init__(self, config):
2096 Coloring.__init__(self, config, 'reposync')
2097 self.project = self.printer('header', attr = 'bold')
2098 self.info = self.printer('info')
2099 self.fail = self.printer('fail', fg='red')
2100
2101class SyncBuffer(object):
2102 def __init__(self, config, detach_head=False):
2103 self._messages = []
2104 self._failures = []
2105 self._later_queue1 = []
2106 self._later_queue2 = []
2107
2108 self.out = _SyncColoring(config)
2109 self.out.redirect(sys.stderr)
2110
2111 self.detach_head = detach_head
2112 self.clean = True
2113
2114 def info(self, project, fmt, *args):
2115 self._messages.append(_InfoMessage(project, fmt % args))
2116
2117 def fail(self, project, err=None):
2118 self._failures.append(_Failure(project, err))
2119 self.clean = False
2120
2121 def later1(self, project, what):
2122 self._later_queue1.append(_Later(project, what))
2123
2124 def later2(self, project, what):
2125 self._later_queue2.append(_Later(project, what))
2126
2127 def Finish(self):
2128 self._PrintMessages()
2129 self._RunLater()
2130 self._PrintMessages()
2131 return self.clean
2132
2133 def _RunLater(self):
2134 for q in ['_later_queue1', '_later_queue2']:
2135 if not self._RunQueue(q):
2136 return
2137
2138 def _RunQueue(self, queue):
2139 for m in getattr(self, queue):
2140 if not m.Run(self):
2141 self.clean = False
2142 return False
2143 setattr(self, queue, [])
2144 return True
2145
2146 def _PrintMessages(self):
2147 for m in self._messages:
2148 m.Print(self)
2149 for m in self._failures:
2150 m.Print(self)
2151
2152 self._messages = []
2153 self._failures = []
2154
2155
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002156class MetaProject(Project):
2157 """A special project housed under .repo.
2158 """
2159 def __init__(self, manifest, name, gitdir, worktree):
2160 repodir = manifest.repodir
2161 Project.__init__(self,
2162 manifest = manifest,
2163 name = name,
2164 gitdir = gitdir,
2165 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002166 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002167 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002168 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002169 revisionId = None,
2170 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002171
2172 def PreSync(self):
2173 if self.Exists:
2174 cb = self.CurrentBranch
2175 if cb:
2176 base = self.GetBranch(cb).merge
2177 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002178 self.revisionExpr = base
2179 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002180
Florian Vallee5d016502012-06-07 17:19:26 +02002181 def MetaBranchSwitch(self, target):
2182 """ Prepare MetaProject for manifest branch switch
2183 """
2184
2185 # detach and delete manifest branch, allowing a new
2186 # branch to take over
2187 syncbuf = SyncBuffer(self.config, detach_head = True)
2188 self.Sync_LocalHalf(syncbuf)
2189 syncbuf.Finish()
2190
2191 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002192 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002193 capture_stdout = True,
2194 capture_stderr = True).Wait() == 0
2195
2196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002197 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002198 def LastFetch(self):
2199 try:
2200 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2201 return os.path.getmtime(fh)
2202 except OSError:
2203 return 0
2204
2205 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002206 def HasChanges(self):
2207 """Has the remote received new commits not yet checked out?
2208 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002209 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002210 return False
2211
2212 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002213 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002214 head = self.work_git.GetHead()
2215 if head.startswith(R_HEADS):
2216 try:
2217 head = all[head]
2218 except KeyError:
2219 head = None
2220
2221 if revid == head:
2222 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002223 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002224 return True
2225 return False