blob: 4f0345f86ea0d1773ed215c8152224d62ccdfcf1 [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
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700179 def UploadForReview(self, people, auto_topic=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,
182 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700184 def GetPublishedRefs(self):
185 refs = {}
186 output = self.project.bare_git.ls_remote(
187 self.branch.remote.SshReviewUrl(self.project.UserEmail),
188 'refs/changes/*')
189 for line in output.split('\n'):
190 try:
191 (sha, ref) = line.split()
192 refs[sha] = ref
193 except ValueError:
194 pass
195
196 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
198class StatusColoring(Coloring):
199 def __init__(self, config):
200 Coloring.__init__(self, config, 'status')
201 self.project = self.printer('header', attr = 'bold')
202 self.branch = self.printer('header', attr = 'bold')
203 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700204 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206 self.added = self.printer('added', fg = 'green')
207 self.changed = self.printer('changed', fg = 'red')
208 self.untracked = self.printer('untracked', fg = 'red')
209
210
211class DiffColoring(Coloring):
212 def __init__(self, config):
213 Coloring.__init__(self, config, 'diff')
214 self.project = self.printer('header', attr = 'bold')
215
James W. Mills24c13082012-04-12 15:04:13 -0500216class _Annotation:
217 def __init__(self, name, value, keep):
218 self.name = name
219 self.value = value
220 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
222class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800223 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 self.src = src
225 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800226 self.abs_src = abssrc
227 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228
229 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800230 src = self.abs_src
231 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232 # copy file if it does not exist or is out of date
233 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
234 try:
235 # remove existing file first, since it might be read-only
236 if os.path.exists(dest):
237 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400238 else:
239 dir = os.path.dirname(dest)
240 if not os.path.isdir(dir):
241 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 shutil.copy(src, dest)
243 # make the file read-only
244 mode = os.stat(dest)[stat.ST_MODE]
245 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
246 os.chmod(dest, mode)
247 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700248 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700250class RemoteSpec(object):
251 def __init__(self,
252 name,
253 url = None,
254 review = None):
255 self.name = name
256 self.url = url
257 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258
Doug Anderson37282b42011-03-04 11:54:18 -0800259class RepoHook(object):
260 """A RepoHook contains information about a script to run as a hook.
261
262 Hooks are used to run a python script before running an upload (for instance,
263 to run presubmit checks). Eventually, we may have hooks for other actions.
264
265 This shouldn't be confused with files in the 'repo/hooks' directory. Those
266 files are copied into each '.git/hooks' folder for each project. Repo-level
267 hooks are associated instead with repo actions.
268
269 Hooks are always python. When a hook is run, we will load the hook into the
270 interpreter and execute its main() function.
271 """
272 def __init__(self,
273 hook_type,
274 hooks_project,
275 topdir,
276 abort_if_user_denies=False):
277 """RepoHook constructor.
278
279 Params:
280 hook_type: A string representing the type of hook. This is also used
281 to figure out the name of the file containing the hook. For
282 example: 'pre-upload'.
283 hooks_project: The project containing the repo hooks. If you have a
284 manifest, this is manifest.repo_hooks_project. OK if this is None,
285 which will make the hook a no-op.
286 topdir: Repo's top directory (the one containing the .repo directory).
287 Scripts will run with CWD as this directory. If you have a manifest,
288 this is manifest.topdir
289 abort_if_user_denies: If True, we'll throw a HookError() if the user
290 doesn't allow us to run the hook.
291 """
292 self._hook_type = hook_type
293 self._hooks_project = hooks_project
294 self._topdir = topdir
295 self._abort_if_user_denies = abort_if_user_denies
296
297 # Store the full path to the script for convenience.
298 if self._hooks_project:
299 self._script_fullpath = os.path.join(self._hooks_project.worktree,
300 self._hook_type + '.py')
301 else:
302 self._script_fullpath = None
303
304 def _GetHash(self):
305 """Return a hash of the contents of the hooks directory.
306
307 We'll just use git to do this. This hash has the property that if anything
308 changes in the directory we will return a different has.
309
310 SECURITY CONSIDERATION:
311 This hash only represents the contents of files in the hook directory, not
312 any other files imported or called by hooks. Changes to imported files
313 can change the script behavior without affecting the hash.
314
315 Returns:
316 A string representing the hash. This will always be ASCII so that it can
317 be printed to the user easily.
318 """
319 assert self._hooks_project, "Must have hooks to calculate their hash."
320
321 # We will use the work_git object rather than just calling GetRevisionId().
322 # That gives us a hash of the latest checked in version of the files that
323 # the user will actually be executing. Specifically, GetRevisionId()
324 # doesn't appear to change even if a user checks out a different version
325 # of the hooks repo (via git checkout) nor if a user commits their own revs.
326 #
327 # NOTE: Local (non-committed) changes will not be factored into this hash.
328 # I think this is OK, since we're really only worried about warning the user
329 # about upstream changes.
330 return self._hooks_project.work_git.rev_parse('HEAD')
331
332 def _GetMustVerb(self):
333 """Return 'must' if the hook is required; 'should' if not."""
334 if self._abort_if_user_denies:
335 return 'must'
336 else:
337 return 'should'
338
339 def _CheckForHookApproval(self):
340 """Check to see whether this hook has been approved.
341
342 We'll look at the hash of all of the hooks. If this matches the hash that
343 the user last approved, we're done. If it doesn't, we'll ask the user
344 about approval.
345
346 Note that we ask permission for each individual hook even though we use
347 the hash of all hooks when detecting changes. We'd like the user to be
348 able to approve / deny each hook individually. We only use the hash of all
349 hooks because there is no other easy way to detect changes to local imports.
350
351 Returns:
352 True if this hook is approved to run; False otherwise.
353
354 Raises:
355 HookError: Raised if the user doesn't approve and abort_if_user_denies
356 was passed to the consturctor.
357 """
358 hooks_dir = self._hooks_project.worktree
359 hooks_config = self._hooks_project.config
360 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
361
362 # Get the last hash that the user approved for this hook; may be None.
363 old_hash = hooks_config.GetString(git_approval_key)
364
365 # Get the current hash so we can tell if scripts changed since approval.
366 new_hash = self._GetHash()
367
368 if old_hash is not None:
369 # User previously approved hook and asked not to be prompted again.
370 if new_hash == old_hash:
371 # Approval matched. We're done.
372 return True
373 else:
374 # Give the user a reason why we're prompting, since they last told
375 # us to "never ask again".
376 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
377 self._hook_type)
378 else:
379 prompt = ''
380
381 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
382 if sys.stdout.isatty():
383 prompt += ('Repo %s run the script:\n'
384 ' %s\n'
385 '\n'
386 'Do you want to allow this script to run '
387 '(yes/yes-never-ask-again/NO)? ') % (
388 self._GetMustVerb(), self._script_fullpath)
389 response = raw_input(prompt).lower()
390 print
391
392 # User is doing a one-time approval.
393 if response in ('y', 'yes'):
394 return True
395 elif response == 'yes-never-ask-again':
396 hooks_config.SetString(git_approval_key, new_hash)
397 return True
398
399 # For anything else, we'll assume no approval.
400 if self._abort_if_user_denies:
401 raise HookError('You must allow the %s hook or use --no-verify.' %
402 self._hook_type)
403
404 return False
405
406 def _ExecuteHook(self, **kwargs):
407 """Actually execute the given hook.
408
409 This will run the hook's 'main' function in our python interpreter.
410
411 Args:
412 kwargs: Keyword arguments to pass to the hook. These are often specific
413 to the hook type. For instance, pre-upload hooks will contain
414 a project_list.
415 """
416 # Keep sys.path and CWD stashed away so that we can always restore them
417 # upon function exit.
418 orig_path = os.getcwd()
419 orig_syspath = sys.path
420
421 try:
422 # Always run hooks with CWD as topdir.
423 os.chdir(self._topdir)
424
425 # Put the hook dir as the first item of sys.path so hooks can do
426 # relative imports. We want to replace the repo dir as [0] so
427 # hooks can't import repo files.
428 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
429
430 # Exec, storing global context in the context dict. We catch exceptions
431 # and convert to a HookError w/ just the failing traceback.
432 context = {}
433 try:
434 execfile(self._script_fullpath, context)
435 except Exception:
436 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
437 traceback.format_exc(), self._hook_type))
438
439 # Running the script should have defined a main() function.
440 if 'main' not in context:
441 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
442
443
444 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
445 # We don't actually want hooks to define their main with this argument--
446 # it's there to remind them that their hook should always take **kwargs.
447 # For instance, a pre-upload hook should be defined like:
448 # def main(project_list, **kwargs):
449 #
450 # This allows us to later expand the API without breaking old hooks.
451 kwargs = kwargs.copy()
452 kwargs['hook_should_take_kwargs'] = True
453
454 # Call the main function in the hook. If the hook should cause the
455 # build to fail, it will raise an Exception. We'll catch that convert
456 # to a HookError w/ just the failing traceback.
457 try:
458 context['main'](**kwargs)
459 except Exception:
460 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
461 'above.' % (
462 traceback.format_exc(), self._hook_type))
463 finally:
464 # Restore sys.path and CWD.
465 sys.path = orig_syspath
466 os.chdir(orig_path)
467
468 def Run(self, user_allows_all_hooks, **kwargs):
469 """Run the hook.
470
471 If the hook doesn't exist (because there is no hooks project or because
472 this particular hook is not enabled), this is a no-op.
473
474 Args:
475 user_allows_all_hooks: If True, we will never prompt about running the
476 hook--we'll just assume it's OK to run it.
477 kwargs: Keyword arguments to pass to the hook. These are often specific
478 to the hook type. For instance, pre-upload hooks will contain
479 a project_list.
480
481 Raises:
482 HookError: If there was a problem finding the hook or the user declined
483 to run a required hook (from _CheckForHookApproval).
484 """
485 # No-op if there is no hooks project or if hook is disabled.
486 if ((not self._hooks_project) or
487 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
488 return
489
490 # Bail with a nice error if we can't find the hook.
491 if not os.path.isfile(self._script_fullpath):
492 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
493
494 # Make sure the user is OK with running the hook.
495 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
496 return
497
498 # Run the hook with the same version of python we're using.
499 self._ExecuteHook(**kwargs)
500
501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700502class Project(object):
503 def __init__(self,
504 manifest,
505 name,
506 remote,
507 gitdir,
508 worktree,
509 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700510 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800511 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700512 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700513 groups = None,
514 sync_c = False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.manifest = manifest
516 self.name = name
517 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800518 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800519 if worktree:
520 self.worktree = worktree.replace('\\', '/')
521 else:
522 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700524 self.revisionExpr = revisionExpr
525
526 if revisionId is None \
527 and revisionExpr \
528 and IsId(revisionExpr):
529 self.revisionId = revisionExpr
530 else:
531 self.revisionId = revisionId
532
Mike Pontillod3153822012-02-28 11:53:24 -0800533 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700534 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700535 self.sync_c = sync_c
Mike Pontillod3153822012-02-28 11:53:24 -0800536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500539 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.config = GitConfig.ForRepository(
541 gitdir = self.gitdir,
542 defaults = self.manifest.globalConfig)
543
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800544 if self.worktree:
545 self.work_git = self._GitGetByExec(self, bare=False)
546 else:
547 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700549 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550
Doug Anderson37282b42011-03-04 11:54:18 -0800551 # This will be filled in if a project is later identified to be the
552 # project containing repo hooks.
553 self.enabled_repo_hooks = []
554
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555 @property
556 def Exists(self):
557 return os.path.isdir(self.gitdir)
558
559 @property
560 def CurrentBranch(self):
561 """Obtain the name of the currently checked out branch.
562 The branch name omits the 'refs/heads/' prefix.
563 None is returned if the project is on a detached HEAD.
564 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700565 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 if b.startswith(R_HEADS):
567 return b[len(R_HEADS):]
568 return None
569
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700570 def IsRebaseInProgress(self):
571 w = self.worktree
572 g = os.path.join(w, '.git')
573 return os.path.exists(os.path.join(g, 'rebase-apply')) \
574 or os.path.exists(os.path.join(g, 'rebase-merge')) \
575 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700577 def IsDirty(self, consider_untracked=True):
578 """Is the working directory modified in some way?
579 """
580 self.work_git.update_index('-q',
581 '--unmerged',
582 '--ignore-missing',
583 '--refresh')
584 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
585 return True
586 if self.work_git.DiffZ('diff-files'):
587 return True
588 if consider_untracked and self.work_git.LsOthers():
589 return True
590 return False
591
592 _userident_name = None
593 _userident_email = None
594
595 @property
596 def UserName(self):
597 """Obtain the user's personal name.
598 """
599 if self._userident_name is None:
600 self._LoadUserIdentity()
601 return self._userident_name
602
603 @property
604 def UserEmail(self):
605 """Obtain the user's email address. This is very likely
606 to be their Gerrit login.
607 """
608 if self._userident_email is None:
609 self._LoadUserIdentity()
610 return self._userident_email
611
612 def _LoadUserIdentity(self):
613 u = self.bare_git.var('GIT_COMMITTER_IDENT')
614 m = re.compile("^(.*) <([^>]*)> ").match(u)
615 if m:
616 self._userident_name = m.group(1)
617 self._userident_email = m.group(2)
618 else:
619 self._userident_name = ''
620 self._userident_email = ''
621
622 def GetRemote(self, name):
623 """Get the configuration for a single remote.
624 """
625 return self.config.GetRemote(name)
626
627 def GetBranch(self, name):
628 """Get the configuration for a single branch.
629 """
630 return self.config.GetBranch(name)
631
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700632 def GetBranches(self):
633 """Get all existing local branches.
634 """
635 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700636 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700637 heads = {}
638 pubd = {}
639
640 for name, id in all.iteritems():
641 if name.startswith(R_HEADS):
642 name = name[len(R_HEADS):]
643 b = self.GetBranch(name)
644 b.current = name == current
645 b.published = None
646 b.revision = id
647 heads[name] = b
648
649 for name, id in all.iteritems():
650 if name.startswith(R_PUB):
651 name = name[len(R_PUB):]
652 b = heads.get(name)
653 if b:
654 b.published = id
655
656 return heads
657
Colin Cross5acde752012-03-28 20:15:45 -0700658 def MatchesGroups(self, manifest_groups):
659 """Returns true if the manifest groups specified at init should cause
660 this project to be synced.
661 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700662 All projects are implicitly labelled with "default".
Colin Cross5acde752012-03-28 20:15:45 -0700663
Conley Owens971de8e2012-04-16 10:36:08 -0700664 labels are resolved in order. In the example case of
665 project_groups: "default,group1,group2"
666 manifest_groups: "-group1,group2"
667 the project will be matched.
668 """
Colin Crosseca119e2012-05-24 15:39:14 -0700669 if self.groups is None:
670 return True
Conley Owens971de8e2012-04-16 10:36:08 -0700671 matched = False
672 for group in manifest_groups:
673 if group.startswith('-') and group[1:] in self.groups:
674 matched = False
675 elif group in self.groups:
676 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700677
Conley Owens971de8e2012-04-16 10:36:08 -0700678 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679
680## Status Display ##
681
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500682 def HasChanges(self):
683 """Returns true if there are uncommitted changes.
684 """
685 self.work_git.update_index('-q',
686 '--unmerged',
687 '--ignore-missing',
688 '--refresh')
689 if self.IsRebaseInProgress():
690 return True
691
692 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
693 return True
694
695 if self.work_git.DiffZ('diff-files'):
696 return True
697
698 if self.work_git.LsOthers():
699 return True
700
701 return False
702
Terence Haddock4655e812011-03-31 12:33:34 +0200703 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200705
706 Args:
707 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 """
709 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200710 if output_redir == None:
711 output_redir = sys.stdout
712 print >>output_redir, ''
713 print >>output_redir, 'project %s/' % self.relpath
714 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715 return
716
717 self.work_git.update_index('-q',
718 '--unmerged',
719 '--ignore-missing',
720 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700721 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
723 df = self.work_git.DiffZ('diff-files')
724 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100725 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700726 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727
728 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200729 if not output_redir == None:
730 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731 out.project('project %-40s', self.relpath + '/')
732
733 branch = self.CurrentBranch
734 if branch is None:
735 out.nobranch('(*** NO BRANCH ***)')
736 else:
737 out.branch('branch %s', branch)
738 out.nl()
739
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700740 if rb:
741 out.important('prior sync failed; rebase still in progress')
742 out.nl()
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 paths = list()
745 paths.extend(di.keys())
746 paths.extend(df.keys())
747 paths.extend(do)
748
749 paths = list(set(paths))
750 paths.sort()
751
752 for p in paths:
753 try: i = di[p]
754 except KeyError: i = None
755
756 try: f = df[p]
757 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 if i: i_status = i.status.upper()
760 else: i_status = '-'
761
762 if f: f_status = f.status.lower()
763 else: f_status = '-'
764
765 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800766 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 i.src_path, p, i.level)
768 else:
769 line = ' %s%s\t%s' % (i_status, f_status, p)
770
771 if i and not f:
772 out.added('%s', line)
773 elif (i and f) or (not i and f):
774 out.changed('%s', line)
775 elif not i and not f:
776 out.untracked('%s', line)
777 else:
778 out.write('%s', line)
779 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200780
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700781 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782
pelyad67872d2012-03-28 14:49:58 +0300783 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 """Prints the status of the repository to stdout.
785 """
786 out = DiffColoring(self.config)
787 cmd = ['diff']
788 if out.is_on:
789 cmd.append('--color')
790 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300791 if absolute_paths:
792 cmd.append('--src-prefix=a/%s/' % self.relpath)
793 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794 cmd.append('--')
795 p = GitCommand(self,
796 cmd,
797 capture_stdout = True,
798 capture_stderr = True)
799 has_diff = False
800 for line in p.process.stdout:
801 if not has_diff:
802 out.nl()
803 out.project('project %s/' % self.relpath)
804 out.nl()
805 has_diff = True
806 print line[:-1]
807 p.Wait()
808
809
810## Publish / Upload ##
811
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700812 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 """Was the branch published (uploaded) for code review?
814 If so, returns the SHA-1 hash of the last published
815 state for the branch.
816 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700817 key = R_PUB + branch
818 if all is None:
819 try:
820 return self.bare_git.rev_parse(key)
821 except GitError:
822 return None
823 else:
824 try:
825 return all[key]
826 except KeyError:
827 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700829 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 """Prunes any stale published refs.
831 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700832 if all is None:
833 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 heads = set()
835 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700836 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 if name.startswith(R_HEADS):
838 heads.add(name)
839 elif name.startswith(R_PUB):
840 canrm[name] = id
841
842 for name, id in canrm.iteritems():
843 n = name[len(R_PUB):]
844 if R_HEADS + n not in heads:
845 self.bare_git.DeleteRef(name, id)
846
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700847 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 """List any branches which can be uploaded for review.
849 """
850 heads = {}
851 pubed = {}
852
853 for name, id in self._allrefs.iteritems():
854 if name.startswith(R_HEADS):
855 heads[name[len(R_HEADS):]] = id
856 elif name.startswith(R_PUB):
857 pubed[name[len(R_PUB):]] = id
858
859 ready = []
860 for branch, id in heads.iteritems():
861 if branch in pubed and pubed[branch] == id:
862 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700863 if selected_branch and branch != selected_branch:
864 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800866 rb = self.GetUploadableBranch(branch)
867 if rb:
868 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 return ready
870
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800871 def GetUploadableBranch(self, branch_name):
872 """Get a single uploadable branch, or None.
873 """
874 branch = self.GetBranch(branch_name)
875 base = branch.LocalMerge
876 if branch.LocalMerge:
877 rb = ReviewableBranch(self, branch, base)
878 if rb.commits:
879 return rb
880 return None
881
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700882 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700883 people=([],[]),
884 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 """Uploads the named branch for code review.
886 """
887 if branch is None:
888 branch = self.CurrentBranch
889 if branch is None:
890 raise GitError('not currently on a branch')
891
892 branch = self.GetBranch(branch)
893 if not branch.LocalMerge:
894 raise GitError('branch %s does not track a remote' % branch.name)
895 if not branch.remote.review:
896 raise GitError('remote %s has no review url' % branch.remote.name)
897
898 dest_branch = branch.merge
899 if not dest_branch.startswith(R_HEADS):
900 dest_branch = R_HEADS + dest_branch
901
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800902 if not branch.remote.projectname:
903 branch.remote.projectname = self.name
904 branch.remote.Save()
905
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800906 url = branch.remote.ReviewUrl(self.UserEmail)
907 if url is None:
908 raise UploadError('review not configured')
909 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800910
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800911 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800912 rp = ['gerrit receive-pack']
913 for e in people[0]:
914 rp.append('--reviewer=%s' % sq(e))
915 for e in people[1]:
916 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800917 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700918
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800919 cmd.append(url)
920
921 if dest_branch.startswith(R_HEADS):
922 dest_branch = dest_branch[len(R_HEADS):]
923 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
924 if auto_topic:
925 ref_spec = ref_spec + '/' + branch.name
926 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800927
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800928 if GitCommand(self, cmd, bare = True).Wait() != 0:
929 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930
931 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
932 self.bare_git.UpdateRef(R_PUB + branch.name,
933 R_HEADS + branch.name,
934 message = msg)
935
936
937## Sync ##
938
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700939 def Sync_NetworkHalf(self,
940 quiet=False,
941 is_new=None,
942 current_branch_only=False,
943 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 """Perform only the network IO portion of the sync process.
945 Local working directory/branch state is not affected.
946 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700947 if is_new is None:
948 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200949 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 self._InitGitDir()
951 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700952
953 if is_new:
954 alt = os.path.join(self.gitdir, 'objects/info/alternates')
955 try:
956 fd = open(alt, 'rb')
957 try:
958 alt_dir = fd.readline().rstrip()
959 finally:
960 fd.close()
961 except IOError:
962 alt_dir = None
963 else:
964 alt_dir = None
965
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700966 if clone_bundle \
967 and alt_dir is None \
968 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700969 is_new = False
970
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700971 if not current_branch_only:
972 if self.sync_c:
973 current_branch_only = True
974 elif not self.manifest._loaded:
975 # Manifest cannot check defaults until it syncs.
976 current_branch_only = False
977 elif self.manifest.default.sync_c:
978 current_branch_only = True
979
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700980 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
981 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800983
984 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800985 self._InitMRef()
986 else:
987 self._InitMirrorHead()
988 try:
989 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
990 except OSError:
991 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800993
994 def PostRepoUpgrade(self):
995 self._InitHooks()
996
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 def _CopyFiles(self):
998 for file in self.copyfiles:
999 file._Copy()
1000
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001001 def GetRevisionId(self, all=None):
1002 if self.revisionId:
1003 return self.revisionId
1004
1005 rem = self.GetRemote(self.remote.name)
1006 rev = rem.ToLocal(self.revisionExpr)
1007
1008 if all is not None and rev in all:
1009 return all[rev]
1010
1011 try:
1012 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1013 except GitError:
1014 raise ManifestInvalidRevisionError(
1015 'revision %s in %s not found' % (self.revisionExpr,
1016 self.name))
1017
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001018 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 """Perform only the local IO portion of the sync process.
1020 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001021 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001022 all = self.bare_ref.all
1023 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001024 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001025
1026 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001027 head = self.work_git.GetHead()
1028 if head.startswith(R_HEADS):
1029 branch = head[len(R_HEADS):]
1030 try:
1031 head = all[head]
1032 except KeyError:
1033 head = None
1034 else:
1035 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001037 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 # Currently on a detached HEAD. The user is assumed to
1039 # not have any local modifications worth worrying about.
1040 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001041 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001042 syncbuf.fail(self, _PriorSyncFailedError())
1043 return
1044
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 if head == revid:
1046 # No changes; don't do anything further.
1047 #
1048 return
1049
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001050 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001052 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001054 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001055 except GitError, e:
1056 syncbuf.fail(self, e)
1057 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001059 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001061 if head == revid:
1062 # No changes; don't do anything further.
1063 #
1064 return
1065
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001068 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001070 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001072 syncbuf.info(self,
1073 "leaving %s; does not track upstream",
1074 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001076 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001077 except GitError, e:
1078 syncbuf.fail(self, e)
1079 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001081 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001084 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 if not_merged:
1088 if upstream_gain:
1089 # The user has published this branch and some of those
1090 # commits are not yet merged upstream. We do not want
1091 # to rewrite the published commits so we punt.
1092 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001093 syncbuf.fail(self,
1094 "branch %s is published (but not merged) and is now %d commits behind"
1095 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001096 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001097 elif pub == head:
1098 # All published commits are merged, and thus we are a
1099 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001100 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001101 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001102 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 self._CopyFiles()
1104 syncbuf.later1(self, _doff)
1105 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001107 # Examine the local commits not in the remote. Find the
1108 # last one attributed to this user, if any.
1109 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001110 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001111 last_mine = None
1112 cnt_mine = 0
1113 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001114 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001115 if committer_email == self.UserEmail:
1116 last_mine = commit_id
1117 cnt_mine += 1
1118
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001119 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121
1122 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001123 syncbuf.fail(self, _DirtyError())
1124 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001126 # If the upstream switched on us, warn the user.
1127 #
1128 if branch.merge != self.revisionExpr:
1129 if branch.merge and self.revisionExpr:
1130 syncbuf.info(self,
1131 'manifest switched %s...%s',
1132 branch.merge,
1133 self.revisionExpr)
1134 elif branch.merge:
1135 syncbuf.info(self,
1136 'manifest no longer tracks %s',
1137 branch.merge)
1138
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001139 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001141 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001143 syncbuf.info(self,
1144 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001145 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001147 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001148 if not ID_RE.match(self.revisionExpr):
1149 # in case of manifest sync the revisionExpr might be a SHA1
1150 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 branch.Save()
1152
Mike Pontillod3153822012-02-28 11:53:24 -08001153 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 self._CopyFiles()
1157 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001158 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001160 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001161 self._CopyFiles()
1162 except GitError, e:
1163 syncbuf.fail(self, e)
1164 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001167 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 self._CopyFiles()
1169 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001171 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 # dest should already be an absolute path, but src is project relative
1173 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001174 abssrc = os.path.join(self.worktree, src)
1175 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
James W. Mills24c13082012-04-12 15:04:13 -05001177 def AddAnnotation(self, name, value, keep):
1178 self.annotations.append(_Annotation(name, value, keep))
1179
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001180 def DownloadPatchSet(self, change_id, patch_id):
1181 """Download a single patch set of a single change to FETCH_HEAD.
1182 """
1183 remote = self.GetRemote(self.remote.name)
1184
1185 cmd = ['fetch', remote.name]
1186 cmd.append('refs/changes/%2.2d/%d/%d' \
1187 % (change_id % 100, change_id, patch_id))
1188 cmd.extend(map(lambda x: str(x), remote.fetch))
1189 if GitCommand(self, cmd, bare=True).Wait() != 0:
1190 return None
1191 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001192 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001193 change_id,
1194 patch_id,
1195 self.bare_git.rev_parse('FETCH_HEAD'))
1196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001197
1198## Branch Management ##
1199
1200 def StartBranch(self, name):
1201 """Create a new branch off the manifest's revision.
1202 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001203 head = self.work_git.GetHead()
1204 if head == (R_HEADS + name):
1205 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001207 all = self.bare_ref.all
1208 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001209 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001210 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001211 capture_stdout = True,
1212 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001213
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001214 branch = self.GetBranch(name)
1215 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001216 branch.merge = self.revisionExpr
1217 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001218
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001219 if head.startswith(R_HEADS):
1220 try:
1221 head = all[head]
1222 except KeyError:
1223 head = None
1224
1225 if revid and head and revid == head:
1226 ref = os.path.join(self.gitdir, R_HEADS + name)
1227 try:
1228 os.makedirs(os.path.dirname(ref))
1229 except OSError:
1230 pass
1231 _lwrite(ref, '%s\n' % revid)
1232 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1233 'ref: %s%s\n' % (R_HEADS, name))
1234 branch.Save()
1235 return True
1236
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001237 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001238 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001239 capture_stdout = True,
1240 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001241 branch.Save()
1242 return True
1243 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244
Wink Saville02d79452009-04-10 13:01:24 -07001245 def CheckoutBranch(self, name):
1246 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001247
1248 Args:
1249 name: The name of the branch to checkout.
1250
1251 Returns:
1252 True if the checkout succeeded; False if it didn't; None if the branch
1253 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001254 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001255 rev = R_HEADS + name
1256 head = self.work_git.GetHead()
1257 if head == rev:
1258 # Already on the branch
1259 #
1260 return True
Wink Saville02d79452009-04-10 13:01:24 -07001261
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001263 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001264 revid = all[rev]
1265 except KeyError:
1266 # Branch does not exist in this project
1267 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001268 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001269
1270 if head.startswith(R_HEADS):
1271 try:
1272 head = all[head]
1273 except KeyError:
1274 head = None
1275
1276 if head == revid:
1277 # Same revision; just update HEAD to point to the new
1278 # target branch, but otherwise take no other action.
1279 #
1280 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1281 'ref: %s%s\n' % (R_HEADS, name))
1282 return True
Wink Saville02d79452009-04-10 13:01:24 -07001283
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001284 return GitCommand(self,
1285 ['checkout', name, '--'],
1286 capture_stdout = True,
1287 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001288
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001289 def AbandonBranch(self, name):
1290 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001291
1292 Args:
1293 name: The name of the branch to abandon.
1294
1295 Returns:
1296 True if the abandon succeeded; False if it didn't; None if the branch
1297 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001298 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001299 rev = R_HEADS + name
1300 all = self.bare_ref.all
1301 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001302 # Doesn't exist
1303 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001304
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001305 head = self.work_git.GetHead()
1306 if head == rev:
1307 # We can't destroy the branch while we are sitting
1308 # on it. Switch to a detached HEAD.
1309 #
1310 head = all[head]
1311
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 revid = self.GetRevisionId(all)
1313 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001314 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1315 '%s\n' % revid)
1316 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001318
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001319 return GitCommand(self,
1320 ['branch', '-D', name],
1321 capture_stdout = True,
1322 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001323
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 def PruneHeads(self):
1325 """Prune any topic branches already merged into upstream.
1326 """
1327 cb = self.CurrentBranch
1328 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001329 left = self._allrefs
1330 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 if name.startswith(R_HEADS):
1332 name = name[len(R_HEADS):]
1333 if cb is None or name != cb:
1334 kill.append(name)
1335
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001336 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 if cb is not None \
1338 and not self._revlist(HEAD + '...' + rev) \
1339 and not self.IsDirty(consider_untracked = False):
1340 self.work_git.DetachHead(HEAD)
1341 kill.append(cb)
1342
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001344 old = self.bare_git.GetHead()
1345 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348 try:
1349 self.bare_git.DetachHead(rev)
1350
1351 b = ['branch', '-d']
1352 b.extend(kill)
1353 b = GitCommand(self, b, bare=True,
1354 capture_stdout=True,
1355 capture_stderr=True)
1356 b.Wait()
1357 finally:
1358 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001359 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001361 for branch in kill:
1362 if (R_HEADS + branch) not in left:
1363 self.CleanPublishedCache()
1364 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365
1366 if cb and cb not in kill:
1367 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001368 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
1370 kept = []
1371 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001372 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 branch = self.GetBranch(branch)
1374 base = branch.LocalMerge
1375 if not base:
1376 base = rev
1377 kept.append(ReviewableBranch(self, branch, base))
1378 return kept
1379
1380
1381## Direct Git Commands ##
1382
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001383 def _RemoteFetch(self, name=None,
1384 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001385 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001386 quiet=False,
1387 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001388
1389 is_sha1 = False
1390 tag_name = None
1391
1392 if current_branch_only:
1393 if ID_RE.match(self.revisionExpr) is not None:
1394 is_sha1 = True
1395 elif self.revisionExpr.startswith(R_TAGS):
1396 # this is a tag and its sha1 value should never change
1397 tag_name = self.revisionExpr[len(R_TAGS):]
1398
1399 if is_sha1 or tag_name is not None:
1400 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001401 # if revision (sha or tag) is not present then following function
1402 # throws an error.
1403 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001404 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001405 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001406 # There is no such persistent revision. We have to fetch it.
1407 pass
1408
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409 if not name:
1410 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001411
1412 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001413 remote = self.GetRemote(name)
1414 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001415 ssh_proxy = True
1416
Shawn O. Pearce88443382010-10-08 10:02:09 +02001417 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001418 if alt_dir and 'objects' == os.path.basename(alt_dir):
1419 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001420 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1421 remote = self.GetRemote(name)
1422
1423 all = self.bare_ref.all
1424 ids = set(all.values())
1425 tmp = set()
1426
1427 for r, id in GitRefs(ref_dir).all.iteritems():
1428 if r not in all:
1429 if r.startswith(R_TAGS) or remote.WritesTo(r):
1430 all[r] = id
1431 ids.add(id)
1432 continue
1433
1434 if id in ids:
1435 continue
1436
1437 r = 'refs/_alt/%s' % id
1438 all[r] = id
1439 ids.add(id)
1440 tmp.add(r)
1441
1442 ref_names = list(all.keys())
1443 ref_names.sort()
1444
1445 tmp_packed = ''
1446 old_packed = ''
1447
1448 for r in ref_names:
1449 line = '%s %s\n' % (all[r], r)
1450 tmp_packed += line
1451 if r not in tmp:
1452 old_packed += line
1453
1454 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001455 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001456 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001457
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001458 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001459
1460 # The --depth option only affects the initial fetch; after that we'll do
1461 # full fetches of changes.
1462 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1463 if depth and initial:
1464 cmd.append('--depth=%s' % depth)
1465
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001466 if quiet:
1467 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001468 if not self.worktree:
1469 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001470 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001471
1472 if not current_branch_only or is_sha1:
1473 # Fetch whole repo
1474 cmd.append('--tags')
1475 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1476 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001477 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001478 cmd.append(tag_name)
1479 else:
1480 branch = self.revisionExpr
1481 if branch.startswith(R_HEADS):
1482 branch = branch[len(R_HEADS):]
1483 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001484
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001485 ok = False
1486 for i in range(2):
1487 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1488 ok = True
1489 break
1490 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001491
1492 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001494 if old_packed != '':
1495 _lwrite(packed_refs, old_packed)
1496 else:
1497 os.remove(packed_refs)
1498 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 return ok
1500
1501 def _ApplyCloneBundle(self, initial=False, quiet=False):
1502 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1503 return False
1504
1505 remote = self.GetRemote(self.remote.name)
1506 bundle_url = remote.url + '/clone.bundle'
1507 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001508 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1509 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001510 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1511 return False
1512
1513 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1514 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001515
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001516 exist_dst = os.path.exists(bundle_dst)
1517 exist_tmp = os.path.exists(bundle_tmp)
1518
1519 if not initial and not exist_dst and not exist_tmp:
1520 return False
1521
1522 if not exist_dst:
1523 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1524 if not exist_dst:
1525 return False
1526
1527 cmd = ['fetch']
1528 if quiet:
1529 cmd.append('--quiet')
1530 if not self.worktree:
1531 cmd.append('--update-head-ok')
1532 cmd.append(bundle_dst)
1533 for f in remote.fetch:
1534 cmd.append(str(f))
1535 cmd.append('refs/tags/*:refs/tags/*')
1536
1537 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001538 if os.path.exists(bundle_dst):
1539 os.remove(bundle_dst)
1540 if os.path.exists(bundle_tmp):
1541 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001542 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001543
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001544 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001545 keep = True
1546 done = False
1547 dest = open(tmpPath, 'a+b')
1548 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001549 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001550 pos = dest.tell()
1551
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001552 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001553 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001554 req = urllib2.Request(srcUrl)
1555 if pos > 0:
1556 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001557
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001558 try:
1559 r = urllib2.urlopen(req)
1560 except urllib2.HTTPError, e:
1561 def _content_type():
1562 try:
1563 return e.info()['content-type']
1564 except:
1565 return None
1566
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001567 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001568 keep = False
1569 return False
1570 elif _content_type() == 'text/plain':
1571 try:
1572 msg = e.read()
1573 if len(msg) > 0 and msg[-1] == '\n':
1574 msg = msg[0:-1]
1575 msg = ' (%s)' % msg
1576 except:
1577 msg = ''
1578 else:
1579 try:
1580 from BaseHTTPServer import BaseHTTPRequestHandler
1581 res = BaseHTTPRequestHandler.responses[e.code]
1582 msg = ' (%s: %s)' % (res[0], res[1])
1583 except:
1584 msg = ''
1585 raise DownloadError('HTTP %s%s' % (e.code, msg))
1586 except urllib2.URLError, e:
1587 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1588 finally:
1589 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001590
1591 p = None
1592 try:
Conley Owens43bda842012-03-12 11:25:04 -07001593 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001594 unit = 1 << 10
1595
1596 if size and not quiet:
1597 if size > 1024 * 1.3:
1598 unit = 1 << 20
1599 desc = 'MB'
1600 else:
1601 desc = 'KB'
1602 p = Progress(
1603 'Downloading %s' % self.relpath,
1604 int(size) / unit,
1605 units=desc)
1606 if pos > 0:
1607 p.update(pos / unit)
1608
1609 s = 0
1610 while True:
1611 d = r.read(8192)
1612 if d == '':
1613 done = True
1614 return True
1615 dest.write(d)
1616 if p:
1617 s += len(d)
1618 if s >= unit:
1619 p.update(s / unit)
1620 s = s % unit
1621 if p:
1622 if s >= unit:
1623 p.update(s / unit)
1624 else:
1625 p.update(1)
1626 finally:
1627 r.close()
1628 if p:
1629 p.end()
1630 finally:
1631 dest.close()
1632
1633 if os.path.exists(dstPath):
1634 os.remove(dstPath)
1635 if done:
1636 os.rename(tmpPath, dstPath)
1637 elif not keep:
1638 os.remove(tmpPath)
1639
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001640 def _Checkout(self, rev, quiet=False):
1641 cmd = ['checkout']
1642 if quiet:
1643 cmd.append('-q')
1644 cmd.append(rev)
1645 cmd.append('--')
1646 if GitCommand(self, cmd).Wait() != 0:
1647 if self._allrefs:
1648 raise GitError('%s checkout %s ' % (self.name, rev))
1649
Pierre Tardye5a21222011-03-24 16:28:18 +01001650 def _CherryPick(self, rev, quiet=False):
1651 cmd = ['cherry-pick']
1652 cmd.append(rev)
1653 cmd.append('--')
1654 if GitCommand(self, cmd).Wait() != 0:
1655 if self._allrefs:
1656 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1657
Erwan Mahea94f1622011-08-19 13:56:09 +02001658 def _Revert(self, rev, quiet=False):
1659 cmd = ['revert']
1660 cmd.append('--no-edit')
1661 cmd.append(rev)
1662 cmd.append('--')
1663 if GitCommand(self, cmd).Wait() != 0:
1664 if self._allrefs:
1665 raise GitError('%s revert %s ' % (self.name, rev))
1666
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 def _ResetHard(self, rev, quiet=True):
1668 cmd = ['reset', '--hard']
1669 if quiet:
1670 cmd.append('-q')
1671 cmd.append(rev)
1672 if GitCommand(self, cmd).Wait() != 0:
1673 raise GitError('%s reset --hard %s ' % (self.name, rev))
1674
1675 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001676 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001677 if onto is not None:
1678 cmd.extend(['--onto', onto])
1679 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001680 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681 raise GitError('%s rebase %s ' % (self.name, upstream))
1682
Pierre Tardy3d125942012-05-04 12:18:12 +02001683 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001685 if ffonly:
1686 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687 if GitCommand(self, cmd).Wait() != 0:
1688 raise GitError('%s merge %s ' % (self.name, head))
1689
1690 def _InitGitDir(self):
1691 if not os.path.exists(self.gitdir):
1692 os.makedirs(self.gitdir)
1693 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001694
Shawn O. Pearce88443382010-10-08 10:02:09 +02001695 mp = self.manifest.manifestProject
1696 ref_dir = mp.config.GetString('repo.reference')
1697
1698 if ref_dir:
1699 mirror_git = os.path.join(ref_dir, self.name + '.git')
1700 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1701 self.relpath + '.git')
1702
1703 if os.path.exists(mirror_git):
1704 ref_dir = mirror_git
1705
1706 elif os.path.exists(repo_git):
1707 ref_dir = repo_git
1708
1709 else:
1710 ref_dir = None
1711
1712 if ref_dir:
1713 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1714 os.path.join(ref_dir, 'objects') + '\n')
1715
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001716 if self.manifest.IsMirror:
1717 self.config.SetString('core.bare', 'true')
1718 else:
1719 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720
1721 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001722 try:
1723 to_rm = os.listdir(hooks)
1724 except OSError:
1725 to_rm = []
1726 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001728 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729
1730 m = self.manifest.manifestProject.config
1731 for key in ['user.name', 'user.email']:
1732 if m.Has(key, include_defaults = False):
1733 self.config.SetString(key, m.GetString(key))
1734
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001735 def _InitHooks(self):
1736 hooks = self._gitdir_path('hooks')
1737 if not os.path.exists(hooks):
1738 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001739 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001740 name = os.path.basename(stock_hook)
1741
Victor Boivie65e0f352011-04-18 11:23:29 +02001742 if name in ('commit-msg',) and not self.remote.review \
1743 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001744 # Don't install a Gerrit Code Review hook if this
1745 # project does not appear to use it for reviews.
1746 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001747 # Since the manifest project is one of those, but also
1748 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001749 continue
1750
1751 dst = os.path.join(hooks, name)
1752 if os.path.islink(dst):
1753 continue
1754 if os.path.exists(dst):
1755 if filecmp.cmp(stock_hook, dst, shallow=False):
1756 os.remove(dst)
1757 else:
1758 _error("%s: Not replacing %s hook", self.relpath, name)
1759 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001760 try:
1761 os.symlink(relpath(stock_hook, dst), dst)
1762 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001763 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001764 raise GitError('filesystem must support symlinks')
1765 else:
1766 raise
1767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001769 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001771 remote.url = self.remote.url
1772 remote.review = self.remote.review
1773 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001775 if self.worktree:
1776 remote.ResetFetch(mirror=False)
1777 else:
1778 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001779 remote.Save()
1780
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781 def _InitMRef(self):
1782 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001783 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001785 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001786 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001787
1788 def _InitAnyMRef(self, ref):
1789 cur = self.bare_ref.symref(ref)
1790
1791 if self.revisionId:
1792 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1793 msg = 'manifest set to %s' % self.revisionId
1794 dst = self.revisionId + '^0'
1795 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1796 else:
1797 remote = self.GetRemote(self.remote.name)
1798 dst = remote.ToLocal(self.revisionExpr)
1799 if cur != dst:
1800 msg = 'manifest set to %s' % self.revisionExpr
1801 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001802
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001803 def _InitWorkTree(self):
1804 dotgit = os.path.join(self.worktree, '.git')
1805 if not os.path.exists(dotgit):
1806 os.makedirs(dotgit)
1807
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808 for name in ['config',
1809 'description',
1810 'hooks',
1811 'info',
1812 'logs',
1813 'objects',
1814 'packed-refs',
1815 'refs',
1816 'rr-cache',
1817 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001818 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001819 src = os.path.join(self.gitdir, name)
1820 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001821 if os.path.islink(dst) or not os.path.exists(dst):
1822 os.symlink(relpath(src, dst), dst)
1823 else:
1824 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001825 except OSError, e:
1826 if e.errno == errno.EPERM:
1827 raise GitError('filesystem must support symlinks')
1828 else:
1829 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001831 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832
1833 cmd = ['read-tree', '--reset', '-u']
1834 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001835 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836 if GitCommand(self, cmd).Wait() != 0:
1837 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001838
1839 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1840 if not os.path.exists(rr_cache):
1841 os.makedirs(rr_cache)
1842
Shawn O. Pearce93609662009-04-21 10:50:33 -07001843 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001844
1845 def _gitdir_path(self, path):
1846 return os.path.join(self.gitdir, path)
1847
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001848 def _revlist(self, *args, **kw):
1849 a = []
1850 a.extend(args)
1851 a.append('--')
1852 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001853
1854 @property
1855 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001856 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001857
1858 class _GitGetByExec(object):
1859 def __init__(self, project, bare):
1860 self._project = project
1861 self._bare = bare
1862
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863 def LsOthers(self):
1864 p = GitCommand(self._project,
1865 ['ls-files',
1866 '-z',
1867 '--others',
1868 '--exclude-standard'],
1869 bare = False,
1870 capture_stdout = True,
1871 capture_stderr = True)
1872 if p.Wait() == 0:
1873 out = p.stdout
1874 if out:
1875 return out[:-1].split("\0")
1876 return []
1877
1878 def DiffZ(self, name, *args):
1879 cmd = [name]
1880 cmd.append('-z')
1881 cmd.extend(args)
1882 p = GitCommand(self._project,
1883 cmd,
1884 bare = False,
1885 capture_stdout = True,
1886 capture_stderr = True)
1887 try:
1888 out = p.process.stdout.read()
1889 r = {}
1890 if out:
1891 out = iter(out[:-1].split('\0'))
1892 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001893 try:
1894 info = out.next()
1895 path = out.next()
1896 except StopIteration:
1897 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001898
1899 class _Info(object):
1900 def __init__(self, path, omode, nmode, oid, nid, state):
1901 self.path = path
1902 self.src_path = None
1903 self.old_mode = omode
1904 self.new_mode = nmode
1905 self.old_id = oid
1906 self.new_id = nid
1907
1908 if len(state) == 1:
1909 self.status = state
1910 self.level = None
1911 else:
1912 self.status = state[:1]
1913 self.level = state[1:]
1914 while self.level.startswith('0'):
1915 self.level = self.level[1:]
1916
1917 info = info[1:].split(' ')
1918 info =_Info(path, *info)
1919 if info.status in ('R', 'C'):
1920 info.src_path = info.path
1921 info.path = out.next()
1922 r[info.path] = info
1923 return r
1924 finally:
1925 p.Wait()
1926
1927 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001928 if self._bare:
1929 path = os.path.join(self._project.gitdir, HEAD)
1930 else:
1931 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001932 fd = open(path, 'rb')
1933 try:
1934 line = fd.read()
1935 finally:
1936 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001937 if line.startswith('ref: '):
1938 return line[5:-1]
1939 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001940
1941 def SetHead(self, ref, message=None):
1942 cmdv = []
1943 if message is not None:
1944 cmdv.extend(['-m', message])
1945 cmdv.append(HEAD)
1946 cmdv.append(ref)
1947 self.symbolic_ref(*cmdv)
1948
1949 def DetachHead(self, new, message=None):
1950 cmdv = ['--no-deref']
1951 if message is not None:
1952 cmdv.extend(['-m', message])
1953 cmdv.append(HEAD)
1954 cmdv.append(new)
1955 self.update_ref(*cmdv)
1956
1957 def UpdateRef(self, name, new, old=None,
1958 message=None,
1959 detach=False):
1960 cmdv = []
1961 if message is not None:
1962 cmdv.extend(['-m', message])
1963 if detach:
1964 cmdv.append('--no-deref')
1965 cmdv.append(name)
1966 cmdv.append(new)
1967 if old is not None:
1968 cmdv.append(old)
1969 self.update_ref(*cmdv)
1970
1971 def DeleteRef(self, name, old=None):
1972 if not old:
1973 old = self.rev_parse(name)
1974 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001975 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001976
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001977 def rev_list(self, *args, **kw):
1978 if 'format' in kw:
1979 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1980 else:
1981 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001982 cmdv.extend(args)
1983 p = GitCommand(self._project,
1984 cmdv,
1985 bare = self._bare,
1986 capture_stdout = True,
1987 capture_stderr = True)
1988 r = []
1989 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001990 if line[-1] == '\n':
1991 line = line[:-1]
1992 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993 if p.Wait() != 0:
1994 raise GitError('%s rev-list %s: %s' % (
1995 self._project.name,
1996 str(args),
1997 p.stderr))
1998 return r
1999
2000 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002001 """Allow arbitrary git commands using pythonic syntax.
2002
2003 This allows you to do things like:
2004 git_obj.rev_parse('HEAD')
2005
2006 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2007 run. We'll replace the '_' with a '-' and try to run a git command.
2008 Any other arguments will be passed to the git command.
2009
2010 Args:
2011 name: The name of the git command to call. Any '_' characters will
2012 be replaced with '-'.
2013
2014 Returns:
2015 A callable object that will try to call git with the named command.
2016 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017 name = name.replace('_', '-')
2018 def runner(*args):
2019 cmdv = [name]
2020 cmdv.extend(args)
2021 p = GitCommand(self._project,
2022 cmdv,
2023 bare = self._bare,
2024 capture_stdout = True,
2025 capture_stderr = True)
2026 if p.Wait() != 0:
2027 raise GitError('%s %s: %s' % (
2028 self._project.name,
2029 name,
2030 p.stderr))
2031 r = p.stdout
2032 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2033 return r[:-1]
2034 return r
2035 return runner
2036
2037
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002038class _PriorSyncFailedError(Exception):
2039 def __str__(self):
2040 return 'prior sync failed; rebase still in progress'
2041
2042class _DirtyError(Exception):
2043 def __str__(self):
2044 return 'contains uncommitted changes'
2045
2046class _InfoMessage(object):
2047 def __init__(self, project, text):
2048 self.project = project
2049 self.text = text
2050
2051 def Print(self, syncbuf):
2052 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2053 syncbuf.out.nl()
2054
2055class _Failure(object):
2056 def __init__(self, project, why):
2057 self.project = project
2058 self.why = why
2059
2060 def Print(self, syncbuf):
2061 syncbuf.out.fail('error: %s/: %s',
2062 self.project.relpath,
2063 str(self.why))
2064 syncbuf.out.nl()
2065
2066class _Later(object):
2067 def __init__(self, project, action):
2068 self.project = project
2069 self.action = action
2070
2071 def Run(self, syncbuf):
2072 out = syncbuf.out
2073 out.project('project %s/', self.project.relpath)
2074 out.nl()
2075 try:
2076 self.action()
2077 out.nl()
2078 return True
2079 except GitError, e:
2080 out.nl()
2081 return False
2082
2083class _SyncColoring(Coloring):
2084 def __init__(self, config):
2085 Coloring.__init__(self, config, 'reposync')
2086 self.project = self.printer('header', attr = 'bold')
2087 self.info = self.printer('info')
2088 self.fail = self.printer('fail', fg='red')
2089
2090class SyncBuffer(object):
2091 def __init__(self, config, detach_head=False):
2092 self._messages = []
2093 self._failures = []
2094 self._later_queue1 = []
2095 self._later_queue2 = []
2096
2097 self.out = _SyncColoring(config)
2098 self.out.redirect(sys.stderr)
2099
2100 self.detach_head = detach_head
2101 self.clean = True
2102
2103 def info(self, project, fmt, *args):
2104 self._messages.append(_InfoMessage(project, fmt % args))
2105
2106 def fail(self, project, err=None):
2107 self._failures.append(_Failure(project, err))
2108 self.clean = False
2109
2110 def later1(self, project, what):
2111 self._later_queue1.append(_Later(project, what))
2112
2113 def later2(self, project, what):
2114 self._later_queue2.append(_Later(project, what))
2115
2116 def Finish(self):
2117 self._PrintMessages()
2118 self._RunLater()
2119 self._PrintMessages()
2120 return self.clean
2121
2122 def _RunLater(self):
2123 for q in ['_later_queue1', '_later_queue2']:
2124 if not self._RunQueue(q):
2125 return
2126
2127 def _RunQueue(self, queue):
2128 for m in getattr(self, queue):
2129 if not m.Run(self):
2130 self.clean = False
2131 return False
2132 setattr(self, queue, [])
2133 return True
2134
2135 def _PrintMessages(self):
2136 for m in self._messages:
2137 m.Print(self)
2138 for m in self._failures:
2139 m.Print(self)
2140
2141 self._messages = []
2142 self._failures = []
2143
2144
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145class MetaProject(Project):
2146 """A special project housed under .repo.
2147 """
2148 def __init__(self, manifest, name, gitdir, worktree):
2149 repodir = manifest.repodir
2150 Project.__init__(self,
2151 manifest = manifest,
2152 name = name,
2153 gitdir = gitdir,
2154 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002155 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002156 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002157 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002158 revisionId = None,
2159 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160
2161 def PreSync(self):
2162 if self.Exists:
2163 cb = self.CurrentBranch
2164 if cb:
2165 base = self.GetBranch(cb).merge
2166 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002167 self.revisionExpr = base
2168 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002169
2170 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002171 def LastFetch(self):
2172 try:
2173 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2174 return os.path.getmtime(fh)
2175 except OSError:
2176 return 0
2177
2178 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002179 def HasChanges(self):
2180 """Has the remote received new commits not yet checked out?
2181 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002182 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002183 return False
2184
2185 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002186 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002187 head = self.work_git.GetHead()
2188 if head.startswith(R_HEADS):
2189 try:
2190 head = all[head]
2191 except KeyError:
2192 head = None
2193
2194 if revid == head:
2195 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002196 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002197 return True
2198 return False