blob: 65fefd925e1696e9c94c9b94124bc3978abc9f73 [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".
663
664 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.
Colin Cross5acde752012-03-28 20:15:45 -0700668 """
Conley Owens971de8e2012-04-16 10:36:08 -0700669 matched = False
670 for group in manifest_groups:
671 if group.startswith('-') and group[1:] in self.groups:
672 matched = False
673 elif group in self.groups:
674 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700675
Conley Owens971de8e2012-04-16 10:36:08 -0700676 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677
678## Status Display ##
679
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500680 def HasChanges(self):
681 """Returns true if there are uncommitted changes.
682 """
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
687 if self.IsRebaseInProgress():
688 return True
689
690 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
691 return True
692
693 if self.work_git.DiffZ('diff-files'):
694 return True
695
696 if self.work_git.LsOthers():
697 return True
698
699 return False
700
Terence Haddock4655e812011-03-31 12:33:34 +0200701 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200703
704 Args:
705 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 """
707 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200708 if output_redir == None:
709 output_redir = sys.stdout
710 print >>output_redir, ''
711 print >>output_redir, 'project %s/' % self.relpath
712 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713 return
714
715 self.work_git.update_index('-q',
716 '--unmerged',
717 '--ignore-missing',
718 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700719 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
721 df = self.work_git.DiffZ('diff-files')
722 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100723 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700724 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725
726 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200727 if not output_redir == None:
728 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 out.project('project %-40s', self.relpath + '/')
730
731 branch = self.CurrentBranch
732 if branch is None:
733 out.nobranch('(*** NO BRANCH ***)')
734 else:
735 out.branch('branch %s', branch)
736 out.nl()
737
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700738 if rb:
739 out.important('prior sync failed; rebase still in progress')
740 out.nl()
741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 paths = list()
743 paths.extend(di.keys())
744 paths.extend(df.keys())
745 paths.extend(do)
746
747 paths = list(set(paths))
748 paths.sort()
749
750 for p in paths:
751 try: i = di[p]
752 except KeyError: i = None
753
754 try: f = df[p]
755 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 if i: i_status = i.status.upper()
758 else: i_status = '-'
759
760 if f: f_status = f.status.lower()
761 else: f_status = '-'
762
763 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800764 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 i.src_path, p, i.level)
766 else:
767 line = ' %s%s\t%s' % (i_status, f_status, p)
768
769 if i and not f:
770 out.added('%s', line)
771 elif (i and f) or (not i and f):
772 out.changed('%s', line)
773 elif not i and not f:
774 out.untracked('%s', line)
775 else:
776 out.write('%s', line)
777 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200778
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700779 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
pelyad67872d2012-03-28 14:49:58 +0300781 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 """Prints the status of the repository to stdout.
783 """
784 out = DiffColoring(self.config)
785 cmd = ['diff']
786 if out.is_on:
787 cmd.append('--color')
788 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300789 if absolute_paths:
790 cmd.append('--src-prefix=a/%s/' % self.relpath)
791 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792 cmd.append('--')
793 p = GitCommand(self,
794 cmd,
795 capture_stdout = True,
796 capture_stderr = True)
797 has_diff = False
798 for line in p.process.stdout:
799 if not has_diff:
800 out.nl()
801 out.project('project %s/' % self.relpath)
802 out.nl()
803 has_diff = True
804 print line[:-1]
805 p.Wait()
806
807
808## Publish / Upload ##
809
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700810 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 """Was the branch published (uploaded) for code review?
812 If so, returns the SHA-1 hash of the last published
813 state for the branch.
814 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700815 key = R_PUB + branch
816 if all is None:
817 try:
818 return self.bare_git.rev_parse(key)
819 except GitError:
820 return None
821 else:
822 try:
823 return all[key]
824 except KeyError:
825 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700827 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 """Prunes any stale published refs.
829 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700830 if all is None:
831 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 heads = set()
833 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700834 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 if name.startswith(R_HEADS):
836 heads.add(name)
837 elif name.startswith(R_PUB):
838 canrm[name] = id
839
840 for name, id in canrm.iteritems():
841 n = name[len(R_PUB):]
842 if R_HEADS + n not in heads:
843 self.bare_git.DeleteRef(name, id)
844
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700845 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """List any branches which can be uploaded for review.
847 """
848 heads = {}
849 pubed = {}
850
851 for name, id in self._allrefs.iteritems():
852 if name.startswith(R_HEADS):
853 heads[name[len(R_HEADS):]] = id
854 elif name.startswith(R_PUB):
855 pubed[name[len(R_PUB):]] = id
856
857 ready = []
858 for branch, id in heads.iteritems():
859 if branch in pubed and pubed[branch] == id:
860 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700861 if selected_branch and branch != selected_branch:
862 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800864 rb = self.GetUploadableBranch(branch)
865 if rb:
866 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 return ready
868
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800869 def GetUploadableBranch(self, branch_name):
870 """Get a single uploadable branch, or None.
871 """
872 branch = self.GetBranch(branch_name)
873 base = branch.LocalMerge
874 if branch.LocalMerge:
875 rb = ReviewableBranch(self, branch, base)
876 if rb.commits:
877 return rb
878 return None
879
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700880 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700881 people=([],[]),
882 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883 """Uploads the named branch for code review.
884 """
885 if branch is None:
886 branch = self.CurrentBranch
887 if branch is None:
888 raise GitError('not currently on a branch')
889
890 branch = self.GetBranch(branch)
891 if not branch.LocalMerge:
892 raise GitError('branch %s does not track a remote' % branch.name)
893 if not branch.remote.review:
894 raise GitError('remote %s has no review url' % branch.remote.name)
895
896 dest_branch = branch.merge
897 if not dest_branch.startswith(R_HEADS):
898 dest_branch = R_HEADS + dest_branch
899
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800900 if not branch.remote.projectname:
901 branch.remote.projectname = self.name
902 branch.remote.Save()
903
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800904 url = branch.remote.ReviewUrl(self.UserEmail)
905 if url is None:
906 raise UploadError('review not configured')
907 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800908
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800909 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800910 rp = ['gerrit receive-pack']
911 for e in people[0]:
912 rp.append('--reviewer=%s' % sq(e))
913 for e in people[1]:
914 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800915 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700916
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800917 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800918
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800919 if dest_branch.startswith(R_HEADS):
920 dest_branch = dest_branch[len(R_HEADS):]
921 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
922 if auto_topic:
923 ref_spec = ref_spec + '/' + branch.name
924 cmd.append(ref_spec)
925
926 if GitCommand(self, cmd, bare = True).Wait() != 0:
927 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928
929 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
930 self.bare_git.UpdateRef(R_PUB + branch.name,
931 R_HEADS + branch.name,
932 message = msg)
933
934
935## Sync ##
936
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700937 def Sync_NetworkHalf(self,
938 quiet=False,
939 is_new=None,
940 current_branch_only=False,
941 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 """Perform only the network IO portion of the sync process.
943 Local working directory/branch state is not affected.
944 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700945 if is_new is None:
946 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200947 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 self._InitGitDir()
949 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700950
951 if is_new:
952 alt = os.path.join(self.gitdir, 'objects/info/alternates')
953 try:
954 fd = open(alt, 'rb')
955 try:
956 alt_dir = fd.readline().rstrip()
957 finally:
958 fd.close()
959 except IOError:
960 alt_dir = None
961 else:
962 alt_dir = None
963
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700964 if clone_bundle \
965 and alt_dir is None \
966 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700967 is_new = False
968
Anatol Pomazau79770d22012-04-20 14:41:59 -0700969 current_branch_only = current_branch_only or self.sync_c or self.manifest.default.sync_c
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700970 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
971 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800973
974 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800975 self._InitMRef()
976 else:
977 self._InitMirrorHead()
978 try:
979 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
980 except OSError:
981 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800983
984 def PostRepoUpgrade(self):
985 self._InitHooks()
986
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 def _CopyFiles(self):
988 for file in self.copyfiles:
989 file._Copy()
990
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700991 def GetRevisionId(self, all=None):
992 if self.revisionId:
993 return self.revisionId
994
995 rem = self.GetRemote(self.remote.name)
996 rev = rem.ToLocal(self.revisionExpr)
997
998 if all is not None and rev in all:
999 return all[rev]
1000
1001 try:
1002 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1003 except GitError:
1004 raise ManifestInvalidRevisionError(
1005 'revision %s in %s not found' % (self.revisionExpr,
1006 self.name))
1007
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001008 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 """Perform only the local IO portion of the sync process.
1010 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001012 all = self.bare_ref.all
1013 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001014 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001015
1016 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001017 head = self.work_git.GetHead()
1018 if head.startswith(R_HEADS):
1019 branch = head[len(R_HEADS):]
1020 try:
1021 head = all[head]
1022 except KeyError:
1023 head = None
1024 else:
1025 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001027 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 # Currently on a detached HEAD. The user is assumed to
1029 # not have any local modifications worth worrying about.
1030 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001031 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 syncbuf.fail(self, _PriorSyncFailedError())
1033 return
1034
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001035 if head == revid:
1036 # No changes; don't do anything further.
1037 #
1038 return
1039
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001042 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001044 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001045 except GitError, e:
1046 syncbuf.fail(self, e)
1047 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001048 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001049 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001051 if head == revid:
1052 # No changes; don't do anything further.
1053 #
1054 return
1055
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001058 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001060 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001062 syncbuf.info(self,
1063 "leaving %s; does not track upstream",
1064 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001066 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001067 except GitError, e:
1068 syncbuf.fail(self, e)
1069 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001073 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001074 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001076 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 if not_merged:
1078 if upstream_gain:
1079 # The user has published this branch and some of those
1080 # commits are not yet merged upstream. We do not want
1081 # to rewrite the published commits so we punt.
1082 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001083 syncbuf.fail(self,
1084 "branch %s is published (but not merged) and is now %d commits behind"
1085 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001086 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001087 elif pub == head:
1088 # All published commits are merged, and thus we are a
1089 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001090 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001091 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001092 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001093 self._CopyFiles()
1094 syncbuf.later1(self, _doff)
1095 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001097 # Examine the local commits not in the remote. Find the
1098 # last one attributed to this user, if any.
1099 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001100 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001101 last_mine = None
1102 cnt_mine = 0
1103 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001104 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 if committer_email == self.UserEmail:
1106 last_mine = commit_id
1107 cnt_mine += 1
1108
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001109 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001110 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
1112 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 syncbuf.fail(self, _DirtyError())
1114 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001116 # If the upstream switched on us, warn the user.
1117 #
1118 if branch.merge != self.revisionExpr:
1119 if branch.merge and self.revisionExpr:
1120 syncbuf.info(self,
1121 'manifest switched %s...%s',
1122 branch.merge,
1123 self.revisionExpr)
1124 elif branch.merge:
1125 syncbuf.info(self,
1126 'manifest no longer tracks %s',
1127 branch.merge)
1128
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001129 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001131 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001133 syncbuf.info(self,
1134 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001135 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001137 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001138 if not ID_RE.match(self.revisionExpr):
1139 # in case of manifest sync the revisionExpr might be a SHA1
1140 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 branch.Save()
1142
Mike Pontillod3153822012-02-28 11:53:24 -08001143 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001145 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001146 self._CopyFiles()
1147 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001148 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001151 self._CopyFiles()
1152 except GitError, e:
1153 syncbuf.fail(self, e)
1154 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001157 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001158 self._CopyFiles()
1159 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001161 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 # dest should already be an absolute path, but src is project relative
1163 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001164 abssrc = os.path.join(self.worktree, src)
1165 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
James W. Mills24c13082012-04-12 15:04:13 -05001167 def AddAnnotation(self, name, value, keep):
1168 self.annotations.append(_Annotation(name, value, keep))
1169
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001170 def DownloadPatchSet(self, change_id, patch_id):
1171 """Download a single patch set of a single change to FETCH_HEAD.
1172 """
1173 remote = self.GetRemote(self.remote.name)
1174
1175 cmd = ['fetch', remote.name]
1176 cmd.append('refs/changes/%2.2d/%d/%d' \
1177 % (change_id % 100, change_id, patch_id))
1178 cmd.extend(map(lambda x: str(x), remote.fetch))
1179 if GitCommand(self, cmd, bare=True).Wait() != 0:
1180 return None
1181 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001183 change_id,
1184 patch_id,
1185 self.bare_git.rev_parse('FETCH_HEAD'))
1186
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
1188## Branch Management ##
1189
1190 def StartBranch(self, name):
1191 """Create a new branch off the manifest's revision.
1192 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001193 head = self.work_git.GetHead()
1194 if head == (R_HEADS + name):
1195 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001197 all = self.bare_ref.all
1198 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001199 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001200 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001201 capture_stdout = True,
1202 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001203
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001204 branch = self.GetBranch(name)
1205 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001206 branch.merge = self.revisionExpr
1207 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001208
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001209 if head.startswith(R_HEADS):
1210 try:
1211 head = all[head]
1212 except KeyError:
1213 head = None
1214
1215 if revid and head and revid == head:
1216 ref = os.path.join(self.gitdir, R_HEADS + name)
1217 try:
1218 os.makedirs(os.path.dirname(ref))
1219 except OSError:
1220 pass
1221 _lwrite(ref, '%s\n' % revid)
1222 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1223 'ref: %s%s\n' % (R_HEADS, name))
1224 branch.Save()
1225 return True
1226
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001227 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001229 capture_stdout = True,
1230 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001231 branch.Save()
1232 return True
1233 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234
Wink Saville02d79452009-04-10 13:01:24 -07001235 def CheckoutBranch(self, name):
1236 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001237
1238 Args:
1239 name: The name of the branch to checkout.
1240
1241 Returns:
1242 True if the checkout succeeded; False if it didn't; None if the branch
1243 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001244 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001245 rev = R_HEADS + name
1246 head = self.work_git.GetHead()
1247 if head == rev:
1248 # Already on the branch
1249 #
1250 return True
Wink Saville02d79452009-04-10 13:01:24 -07001251
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001252 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001253 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001254 revid = all[rev]
1255 except KeyError:
1256 # Branch does not exist in this project
1257 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001258 return None
Wink Saville02d79452009-04-10 13:01:24 -07001259
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 if head.startswith(R_HEADS):
1261 try:
1262 head = all[head]
1263 except KeyError:
1264 head = None
1265
1266 if head == revid:
1267 # Same revision; just update HEAD to point to the new
1268 # target branch, but otherwise take no other action.
1269 #
1270 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1271 'ref: %s%s\n' % (R_HEADS, name))
1272 return True
1273
1274 return GitCommand(self,
1275 ['checkout', name, '--'],
1276 capture_stdout = True,
1277 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001278
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001279 def AbandonBranch(self, name):
1280 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001281
1282 Args:
1283 name: The name of the branch to abandon.
1284
1285 Returns:
1286 True if the abandon succeeded; False if it didn't; None if the branch
1287 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001288 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001289 rev = R_HEADS + name
1290 all = self.bare_ref.all
1291 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001292 # Doesn't exist
1293 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001294
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001295 head = self.work_git.GetHead()
1296 if head == rev:
1297 # We can't destroy the branch while we are sitting
1298 # on it. Switch to a detached HEAD.
1299 #
1300 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001301
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 revid = self.GetRevisionId(all)
1303 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001304 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1305 '%s\n' % revid)
1306 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001308
1309 return GitCommand(self,
1310 ['branch', '-D', name],
1311 capture_stdout = True,
1312 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001313
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 def PruneHeads(self):
1315 """Prune any topic branches already merged into upstream.
1316 """
1317 cb = self.CurrentBranch
1318 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001319 left = self._allrefs
1320 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 if name.startswith(R_HEADS):
1322 name = name[len(R_HEADS):]
1323 if cb is None or name != cb:
1324 kill.append(name)
1325
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001326 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 if cb is not None \
1328 and not self._revlist(HEAD + '...' + rev) \
1329 and not self.IsDirty(consider_untracked = False):
1330 self.work_git.DetachHead(HEAD)
1331 kill.append(cb)
1332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001334 old = self.bare_git.GetHead()
1335 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 try:
1339 self.bare_git.DetachHead(rev)
1340
1341 b = ['branch', '-d']
1342 b.extend(kill)
1343 b = GitCommand(self, b, bare=True,
1344 capture_stdout=True,
1345 capture_stderr=True)
1346 b.Wait()
1347 finally:
1348 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001349 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001351 for branch in kill:
1352 if (R_HEADS + branch) not in left:
1353 self.CleanPublishedCache()
1354 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355
1356 if cb and cb not in kill:
1357 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001358 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359
1360 kept = []
1361 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001362 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 branch = self.GetBranch(branch)
1364 base = branch.LocalMerge
1365 if not base:
1366 base = rev
1367 kept.append(ReviewableBranch(self, branch, base))
1368 return kept
1369
1370
1371## Direct Git Commands ##
1372
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001373 def _RemoteFetch(self, name=None,
1374 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001375 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001376 quiet=False,
1377 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001378
1379 is_sha1 = False
1380 tag_name = None
1381
1382 if current_branch_only:
1383 if ID_RE.match(self.revisionExpr) is not None:
1384 is_sha1 = True
1385 elif self.revisionExpr.startswith(R_TAGS):
1386 # this is a tag and its sha1 value should never change
1387 tag_name = self.revisionExpr[len(R_TAGS):]
1388
1389 if is_sha1 or tag_name is not None:
1390 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001391 # if revision (sha or tag) is not present then following function
1392 # throws an error.
1393 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001394 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001395 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001396 # There is no such persistent revision. We have to fetch it.
1397 pass
1398
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 if not name:
1400 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001401
1402 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001403 remote = self.GetRemote(name)
1404 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001405 ssh_proxy = True
1406
Shawn O. Pearce88443382010-10-08 10:02:09 +02001407 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001408 if alt_dir and 'objects' == os.path.basename(alt_dir):
1409 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001410 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1411 remote = self.GetRemote(name)
1412
1413 all = self.bare_ref.all
1414 ids = set(all.values())
1415 tmp = set()
1416
1417 for r, id in GitRefs(ref_dir).all.iteritems():
1418 if r not in all:
1419 if r.startswith(R_TAGS) or remote.WritesTo(r):
1420 all[r] = id
1421 ids.add(id)
1422 continue
1423
1424 if id in ids:
1425 continue
1426
1427 r = 'refs/_alt/%s' % id
1428 all[r] = id
1429 ids.add(id)
1430 tmp.add(r)
1431
1432 ref_names = list(all.keys())
1433 ref_names.sort()
1434
1435 tmp_packed = ''
1436 old_packed = ''
1437
1438 for r in ref_names:
1439 line = '%s %s\n' % (all[r], r)
1440 tmp_packed += line
1441 if r not in tmp:
1442 old_packed += line
1443
1444 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001445 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001446 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001447
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001448 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001449
1450 # The --depth option only affects the initial fetch; after that we'll do
1451 # full fetches of changes.
1452 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1453 if depth and initial:
1454 cmd.append('--depth=%s' % depth)
1455
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001456 if quiet:
1457 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001458 if not self.worktree:
1459 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001460 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001461
1462 if not current_branch_only or is_sha1:
1463 # Fetch whole repo
1464 cmd.append('--tags')
1465 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1466 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001467 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001468 cmd.append(tag_name)
1469 else:
1470 branch = self.revisionExpr
1471 if branch.startswith(R_HEADS):
1472 branch = branch[len(R_HEADS):]
1473 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001474
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001475 ok = False
1476 for i in range(2):
1477 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1478 ok = True
1479 break
1480 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001481
1482 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001484 if old_packed != '':
1485 _lwrite(packed_refs, old_packed)
1486 else:
1487 os.remove(packed_refs)
1488 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001489 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001490
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001491 def _ApplyCloneBundle(self, initial=False, quiet=False):
1492 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1493 return False
1494
1495 remote = self.GetRemote(self.remote.name)
1496 bundle_url = remote.url + '/clone.bundle'
1497 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001498 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1499 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1501 return False
1502
1503 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1504 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1505
1506 exist_dst = os.path.exists(bundle_dst)
1507 exist_tmp = os.path.exists(bundle_tmp)
1508
1509 if not initial and not exist_dst and not exist_tmp:
1510 return False
1511
1512 if not exist_dst:
1513 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1514 if not exist_dst:
1515 return False
1516
1517 cmd = ['fetch']
1518 if quiet:
1519 cmd.append('--quiet')
1520 if not self.worktree:
1521 cmd.append('--update-head-ok')
1522 cmd.append(bundle_dst)
1523 for f in remote.fetch:
1524 cmd.append(str(f))
1525 cmd.append('refs/tags/*:refs/tags/*')
1526
1527 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001528 if os.path.exists(bundle_dst):
1529 os.remove(bundle_dst)
1530 if os.path.exists(bundle_tmp):
1531 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001532 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001533
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001534 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001535 keep = True
1536 done = False
1537 dest = open(tmpPath, 'a+b')
1538 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001539 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001540 pos = dest.tell()
1541
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001542 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001543 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001544 req = urllib2.Request(srcUrl)
1545 if pos > 0:
1546 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001547
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001548 try:
1549 r = urllib2.urlopen(req)
1550 except urllib2.HTTPError, e:
1551 def _content_type():
1552 try:
1553 return e.info()['content-type']
1554 except:
1555 return None
1556
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001557 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001558 keep = False
1559 return False
1560 elif _content_type() == 'text/plain':
1561 try:
1562 msg = e.read()
1563 if len(msg) > 0 and msg[-1] == '\n':
1564 msg = msg[0:-1]
1565 msg = ' (%s)' % msg
1566 except:
1567 msg = ''
1568 else:
1569 try:
1570 from BaseHTTPServer import BaseHTTPRequestHandler
1571 res = BaseHTTPRequestHandler.responses[e.code]
1572 msg = ' (%s: %s)' % (res[0], res[1])
1573 except:
1574 msg = ''
1575 raise DownloadError('HTTP %s%s' % (e.code, msg))
1576 except urllib2.URLError, e:
1577 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1578 finally:
1579 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001580
1581 p = None
1582 try:
Conley Owens43bda842012-03-12 11:25:04 -07001583 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001584 unit = 1 << 10
1585
1586 if size and not quiet:
1587 if size > 1024 * 1.3:
1588 unit = 1 << 20
1589 desc = 'MB'
1590 else:
1591 desc = 'KB'
1592 p = Progress(
1593 'Downloading %s' % self.relpath,
1594 int(size) / unit,
1595 units=desc)
1596 if pos > 0:
1597 p.update(pos / unit)
1598
1599 s = 0
1600 while True:
1601 d = r.read(8192)
1602 if d == '':
1603 done = True
1604 return True
1605 dest.write(d)
1606 if p:
1607 s += len(d)
1608 if s >= unit:
1609 p.update(s / unit)
1610 s = s % unit
1611 if p:
1612 if s >= unit:
1613 p.update(s / unit)
1614 else:
1615 p.update(1)
1616 finally:
1617 r.close()
1618 if p:
1619 p.end()
1620 finally:
1621 dest.close()
1622
1623 if os.path.exists(dstPath):
1624 os.remove(dstPath)
1625 if done:
1626 os.rename(tmpPath, dstPath)
1627 elif not keep:
1628 os.remove(tmpPath)
1629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001630 def _Checkout(self, rev, quiet=False):
1631 cmd = ['checkout']
1632 if quiet:
1633 cmd.append('-q')
1634 cmd.append(rev)
1635 cmd.append('--')
1636 if GitCommand(self, cmd).Wait() != 0:
1637 if self._allrefs:
1638 raise GitError('%s checkout %s ' % (self.name, rev))
1639
Pierre Tardye5a21222011-03-24 16:28:18 +01001640 def _CherryPick(self, rev, quiet=False):
1641 cmd = ['cherry-pick']
1642 cmd.append(rev)
1643 cmd.append('--')
1644 if GitCommand(self, cmd).Wait() != 0:
1645 if self._allrefs:
1646 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1647
Erwan Mahea94f1622011-08-19 13:56:09 +02001648 def _Revert(self, rev, quiet=False):
1649 cmd = ['revert']
1650 cmd.append('--no-edit')
1651 cmd.append(rev)
1652 cmd.append('--')
1653 if GitCommand(self, cmd).Wait() != 0:
1654 if self._allrefs:
1655 raise GitError('%s revert %s ' % (self.name, rev))
1656
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001657 def _ResetHard(self, rev, quiet=True):
1658 cmd = ['reset', '--hard']
1659 if quiet:
1660 cmd.append('-q')
1661 cmd.append(rev)
1662 if GitCommand(self, cmd).Wait() != 0:
1663 raise GitError('%s reset --hard %s ' % (self.name, rev))
1664
1665 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001666 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 if onto is not None:
1668 cmd.extend(['--onto', onto])
1669 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001670 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671 raise GitError('%s rebase %s ' % (self.name, upstream))
1672
Pierre Tardy3d125942012-05-04 12:18:12 +02001673 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001674 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001675 if ffonly:
1676 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001677 if GitCommand(self, cmd).Wait() != 0:
1678 raise GitError('%s merge %s ' % (self.name, head))
1679
1680 def _InitGitDir(self):
1681 if not os.path.exists(self.gitdir):
1682 os.makedirs(self.gitdir)
1683 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001684
Shawn O. Pearce88443382010-10-08 10:02:09 +02001685 mp = self.manifest.manifestProject
1686 ref_dir = mp.config.GetString('repo.reference')
1687
1688 if ref_dir:
1689 mirror_git = os.path.join(ref_dir, self.name + '.git')
1690 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1691 self.relpath + '.git')
1692
1693 if os.path.exists(mirror_git):
1694 ref_dir = mirror_git
1695
1696 elif os.path.exists(repo_git):
1697 ref_dir = repo_git
1698
1699 else:
1700 ref_dir = None
1701
1702 if ref_dir:
1703 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1704 os.path.join(ref_dir, 'objects') + '\n')
1705
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001706 if self.manifest.IsMirror:
1707 self.config.SetString('core.bare', 'true')
1708 else:
1709 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
1711 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001712 try:
1713 to_rm = os.listdir(hooks)
1714 except OSError:
1715 to_rm = []
1716 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001718 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001719
1720 m = self.manifest.manifestProject.config
1721 for key in ['user.name', 'user.email']:
1722 if m.Has(key, include_defaults = False):
1723 self.config.SetString(key, m.GetString(key))
1724
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001725 def _InitHooks(self):
1726 hooks = self._gitdir_path('hooks')
1727 if not os.path.exists(hooks):
1728 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001729 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001730 name = os.path.basename(stock_hook)
1731
Victor Boivie65e0f352011-04-18 11:23:29 +02001732 if name in ('commit-msg',) and not self.remote.review \
1733 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001734 # Don't install a Gerrit Code Review hook if this
1735 # project does not appear to use it for reviews.
1736 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001737 # Since the manifest project is one of those, but also
1738 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001739 continue
1740
1741 dst = os.path.join(hooks, name)
1742 if os.path.islink(dst):
1743 continue
1744 if os.path.exists(dst):
1745 if filecmp.cmp(stock_hook, dst, shallow=False):
1746 os.remove(dst)
1747 else:
1748 _error("%s: Not replacing %s hook", self.relpath, name)
1749 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001750 try:
1751 os.symlink(relpath(stock_hook, dst), dst)
1752 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001753 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001754 raise GitError('filesystem must support symlinks')
1755 else:
1756 raise
1757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001759 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001761 remote.url = self.remote.url
1762 remote.review = self.remote.review
1763 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001765 if self.worktree:
1766 remote.ResetFetch(mirror=False)
1767 else:
1768 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 remote.Save()
1770
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001771 def _InitMRef(self):
1772 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001773 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001775 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001776 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001777
1778 def _InitAnyMRef(self, ref):
1779 cur = self.bare_ref.symref(ref)
1780
1781 if self.revisionId:
1782 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1783 msg = 'manifest set to %s' % self.revisionId
1784 dst = self.revisionId + '^0'
1785 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1786 else:
1787 remote = self.GetRemote(self.remote.name)
1788 dst = remote.ToLocal(self.revisionExpr)
1789 if cur != dst:
1790 msg = 'manifest set to %s' % self.revisionExpr
1791 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001792
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001793 def _InitWorkTree(self):
1794 dotgit = os.path.join(self.worktree, '.git')
1795 if not os.path.exists(dotgit):
1796 os.makedirs(dotgit)
1797
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 for name in ['config',
1799 'description',
1800 'hooks',
1801 'info',
1802 'logs',
1803 'objects',
1804 'packed-refs',
1805 'refs',
1806 'rr-cache',
1807 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001808 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001809 src = os.path.join(self.gitdir, name)
1810 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001811 if os.path.islink(dst) or not os.path.exists(dst):
1812 os.symlink(relpath(src, dst), dst)
1813 else:
1814 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001815 except OSError, e:
1816 if e.errno == errno.EPERM:
1817 raise GitError('filesystem must support symlinks')
1818 else:
1819 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001821 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822
1823 cmd = ['read-tree', '--reset', '-u']
1824 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001825 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826 if GitCommand(self, cmd).Wait() != 0:
1827 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001828
1829 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1830 if not os.path.exists(rr_cache):
1831 os.makedirs(rr_cache)
1832
Shawn O. Pearce93609662009-04-21 10:50:33 -07001833 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834
1835 def _gitdir_path(self, path):
1836 return os.path.join(self.gitdir, path)
1837
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001838 def _revlist(self, *args, **kw):
1839 a = []
1840 a.extend(args)
1841 a.append('--')
1842 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001843
1844 @property
1845 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001846 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001847
1848 class _GitGetByExec(object):
1849 def __init__(self, project, bare):
1850 self._project = project
1851 self._bare = bare
1852
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001853 def LsOthers(self):
1854 p = GitCommand(self._project,
1855 ['ls-files',
1856 '-z',
1857 '--others',
1858 '--exclude-standard'],
1859 bare = False,
1860 capture_stdout = True,
1861 capture_stderr = True)
1862 if p.Wait() == 0:
1863 out = p.stdout
1864 if out:
1865 return out[:-1].split("\0")
1866 return []
1867
1868 def DiffZ(self, name, *args):
1869 cmd = [name]
1870 cmd.append('-z')
1871 cmd.extend(args)
1872 p = GitCommand(self._project,
1873 cmd,
1874 bare = False,
1875 capture_stdout = True,
1876 capture_stderr = True)
1877 try:
1878 out = p.process.stdout.read()
1879 r = {}
1880 if out:
1881 out = iter(out[:-1].split('\0'))
1882 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001883 try:
1884 info = out.next()
1885 path = out.next()
1886 except StopIteration:
1887 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001888
1889 class _Info(object):
1890 def __init__(self, path, omode, nmode, oid, nid, state):
1891 self.path = path
1892 self.src_path = None
1893 self.old_mode = omode
1894 self.new_mode = nmode
1895 self.old_id = oid
1896 self.new_id = nid
1897
1898 if len(state) == 1:
1899 self.status = state
1900 self.level = None
1901 else:
1902 self.status = state[:1]
1903 self.level = state[1:]
1904 while self.level.startswith('0'):
1905 self.level = self.level[1:]
1906
1907 info = info[1:].split(' ')
1908 info =_Info(path, *info)
1909 if info.status in ('R', 'C'):
1910 info.src_path = info.path
1911 info.path = out.next()
1912 r[info.path] = info
1913 return r
1914 finally:
1915 p.Wait()
1916
1917 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001918 if self._bare:
1919 path = os.path.join(self._project.gitdir, HEAD)
1920 else:
1921 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001922 fd = open(path, 'rb')
1923 try:
1924 line = fd.read()
1925 finally:
1926 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001927 if line.startswith('ref: '):
1928 return line[5:-1]
1929 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001930
1931 def SetHead(self, ref, message=None):
1932 cmdv = []
1933 if message is not None:
1934 cmdv.extend(['-m', message])
1935 cmdv.append(HEAD)
1936 cmdv.append(ref)
1937 self.symbolic_ref(*cmdv)
1938
1939 def DetachHead(self, new, message=None):
1940 cmdv = ['--no-deref']
1941 if message is not None:
1942 cmdv.extend(['-m', message])
1943 cmdv.append(HEAD)
1944 cmdv.append(new)
1945 self.update_ref(*cmdv)
1946
1947 def UpdateRef(self, name, new, old=None,
1948 message=None,
1949 detach=False):
1950 cmdv = []
1951 if message is not None:
1952 cmdv.extend(['-m', message])
1953 if detach:
1954 cmdv.append('--no-deref')
1955 cmdv.append(name)
1956 cmdv.append(new)
1957 if old is not None:
1958 cmdv.append(old)
1959 self.update_ref(*cmdv)
1960
1961 def DeleteRef(self, name, old=None):
1962 if not old:
1963 old = self.rev_parse(name)
1964 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001965 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001966
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001967 def rev_list(self, *args, **kw):
1968 if 'format' in kw:
1969 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1970 else:
1971 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972 cmdv.extend(args)
1973 p = GitCommand(self._project,
1974 cmdv,
1975 bare = self._bare,
1976 capture_stdout = True,
1977 capture_stderr = True)
1978 r = []
1979 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001980 if line[-1] == '\n':
1981 line = line[:-1]
1982 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001983 if p.Wait() != 0:
1984 raise GitError('%s rev-list %s: %s' % (
1985 self._project.name,
1986 str(args),
1987 p.stderr))
1988 return r
1989
1990 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001991 """Allow arbitrary git commands using pythonic syntax.
1992
1993 This allows you to do things like:
1994 git_obj.rev_parse('HEAD')
1995
1996 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1997 run. We'll replace the '_' with a '-' and try to run a git command.
1998 Any other arguments will be passed to the git command.
1999
2000 Args:
2001 name: The name of the git command to call. Any '_' characters will
2002 be replaced with '-'.
2003
2004 Returns:
2005 A callable object that will try to call git with the named command.
2006 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007 name = name.replace('_', '-')
2008 def runner(*args):
2009 cmdv = [name]
2010 cmdv.extend(args)
2011 p = GitCommand(self._project,
2012 cmdv,
2013 bare = self._bare,
2014 capture_stdout = True,
2015 capture_stderr = True)
2016 if p.Wait() != 0:
2017 raise GitError('%s %s: %s' % (
2018 self._project.name,
2019 name,
2020 p.stderr))
2021 r = p.stdout
2022 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2023 return r[:-1]
2024 return r
2025 return runner
2026
2027
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002028class _PriorSyncFailedError(Exception):
2029 def __str__(self):
2030 return 'prior sync failed; rebase still in progress'
2031
2032class _DirtyError(Exception):
2033 def __str__(self):
2034 return 'contains uncommitted changes'
2035
2036class _InfoMessage(object):
2037 def __init__(self, project, text):
2038 self.project = project
2039 self.text = text
2040
2041 def Print(self, syncbuf):
2042 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2043 syncbuf.out.nl()
2044
2045class _Failure(object):
2046 def __init__(self, project, why):
2047 self.project = project
2048 self.why = why
2049
2050 def Print(self, syncbuf):
2051 syncbuf.out.fail('error: %s/: %s',
2052 self.project.relpath,
2053 str(self.why))
2054 syncbuf.out.nl()
2055
2056class _Later(object):
2057 def __init__(self, project, action):
2058 self.project = project
2059 self.action = action
2060
2061 def Run(self, syncbuf):
2062 out = syncbuf.out
2063 out.project('project %s/', self.project.relpath)
2064 out.nl()
2065 try:
2066 self.action()
2067 out.nl()
2068 return True
2069 except GitError, e:
2070 out.nl()
2071 return False
2072
2073class _SyncColoring(Coloring):
2074 def __init__(self, config):
2075 Coloring.__init__(self, config, 'reposync')
2076 self.project = self.printer('header', attr = 'bold')
2077 self.info = self.printer('info')
2078 self.fail = self.printer('fail', fg='red')
2079
2080class SyncBuffer(object):
2081 def __init__(self, config, detach_head=False):
2082 self._messages = []
2083 self._failures = []
2084 self._later_queue1 = []
2085 self._later_queue2 = []
2086
2087 self.out = _SyncColoring(config)
2088 self.out.redirect(sys.stderr)
2089
2090 self.detach_head = detach_head
2091 self.clean = True
2092
2093 def info(self, project, fmt, *args):
2094 self._messages.append(_InfoMessage(project, fmt % args))
2095
2096 def fail(self, project, err=None):
2097 self._failures.append(_Failure(project, err))
2098 self.clean = False
2099
2100 def later1(self, project, what):
2101 self._later_queue1.append(_Later(project, what))
2102
2103 def later2(self, project, what):
2104 self._later_queue2.append(_Later(project, what))
2105
2106 def Finish(self):
2107 self._PrintMessages()
2108 self._RunLater()
2109 self._PrintMessages()
2110 return self.clean
2111
2112 def _RunLater(self):
2113 for q in ['_later_queue1', '_later_queue2']:
2114 if not self._RunQueue(q):
2115 return
2116
2117 def _RunQueue(self, queue):
2118 for m in getattr(self, queue):
2119 if not m.Run(self):
2120 self.clean = False
2121 return False
2122 setattr(self, queue, [])
2123 return True
2124
2125 def _PrintMessages(self):
2126 for m in self._messages:
2127 m.Print(self)
2128 for m in self._failures:
2129 m.Print(self)
2130
2131 self._messages = []
2132 self._failures = []
2133
2134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135class MetaProject(Project):
2136 """A special project housed under .repo.
2137 """
2138 def __init__(self, manifest, name, gitdir, worktree):
2139 repodir = manifest.repodir
2140 Project.__init__(self,
2141 manifest = manifest,
2142 name = name,
2143 gitdir = gitdir,
2144 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002145 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002147 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002148 revisionId = None,
2149 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150
2151 def PreSync(self):
2152 if self.Exists:
2153 cb = self.CurrentBranch
2154 if cb:
2155 base = self.GetBranch(cb).merge
2156 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002157 self.revisionExpr = base
2158 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002159
2160 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002161 def LastFetch(self):
2162 try:
2163 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2164 return os.path.getmtime(fh)
2165 except OSError:
2166 return 0
2167
2168 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002169 def HasChanges(self):
2170 """Has the remote received new commits not yet checked out?
2171 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002172 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002173 return False
2174
2175 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002176 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002177 head = self.work_git.GetHead()
2178 if head.startswith(R_HEADS):
2179 try:
2180 head = all[head]
2181 except KeyError:
2182 head = None
2183
2184 if revid == head:
2185 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002186 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002187 return True
2188 return False