blob: afab1b7c1a534f1c2607dab2682e8bf31c7504f8 [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
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700969 if not current_branch_only:
970 if self.sync_c:
971 current_branch_only = True
972 elif not self.manifest._loaded:
973 # Manifest cannot check defaults until it syncs.
974 current_branch_only = False
975 elif self.manifest.default.sync_c:
976 current_branch_only = True
977
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700978 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
979 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800981
982 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800983 self._InitMRef()
984 else:
985 self._InitMirrorHead()
986 try:
987 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
988 except OSError:
989 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800991
992 def PostRepoUpgrade(self):
993 self._InitHooks()
994
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 def _CopyFiles(self):
996 for file in self.copyfiles:
997 file._Copy()
998
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700999 def GetRevisionId(self, all=None):
1000 if self.revisionId:
1001 return self.revisionId
1002
1003 rem = self.GetRemote(self.remote.name)
1004 rev = rem.ToLocal(self.revisionExpr)
1005
1006 if all is not None and rev in all:
1007 return all[rev]
1008
1009 try:
1010 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1011 except GitError:
1012 raise ManifestInvalidRevisionError(
1013 'revision %s in %s not found' % (self.revisionExpr,
1014 self.name))
1015
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001016 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 """Perform only the local IO portion of the sync process.
1018 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001020 all = self.bare_ref.all
1021 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001022 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001023
1024 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001025 head = self.work_git.GetHead()
1026 if head.startswith(R_HEADS):
1027 branch = head[len(R_HEADS):]
1028 try:
1029 head = all[head]
1030 except KeyError:
1031 head = None
1032 else:
1033 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001035 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 # Currently on a detached HEAD. The user is assumed to
1037 # not have any local modifications worth worrying about.
1038 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001039 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001040 syncbuf.fail(self, _PriorSyncFailedError())
1041 return
1042
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001043 if head == revid:
1044 # No changes; don't do anything further.
1045 #
1046 return
1047
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001048 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001050 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001052 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 except GitError, e:
1054 syncbuf.fail(self, e)
1055 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001057 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001059 if head == revid:
1060 # No changes; don't do anything further.
1061 #
1062 return
1063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001066 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001068 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001070 syncbuf.info(self,
1071 "leaving %s; does not track upstream",
1072 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001074 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001075 except GitError, e:
1076 syncbuf.fail(self, e)
1077 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001079 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001081 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001082 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001084 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 if not_merged:
1086 if upstream_gain:
1087 # The user has published this branch and some of those
1088 # commits are not yet merged upstream. We do not want
1089 # to rewrite the published commits so we punt.
1090 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001091 syncbuf.fail(self,
1092 "branch %s is published (but not merged) and is now %d commits behind"
1093 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001094 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001095 elif pub == head:
1096 # All published commits are merged, and thus we are a
1097 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001098 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001099 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001100 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001101 self._CopyFiles()
1102 syncbuf.later1(self, _doff)
1103 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 # Examine the local commits not in the remote. Find the
1106 # last one attributed to this user, if any.
1107 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001108 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001109 last_mine = None
1110 cnt_mine = 0
1111 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001112 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001113 if committer_email == self.UserEmail:
1114 last_mine = commit_id
1115 cnt_mine += 1
1116
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001117 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001118 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119
1120 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 syncbuf.fail(self, _DirtyError())
1122 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001124 # If the upstream switched on us, warn the user.
1125 #
1126 if branch.merge != self.revisionExpr:
1127 if branch.merge and self.revisionExpr:
1128 syncbuf.info(self,
1129 'manifest switched %s...%s',
1130 branch.merge,
1131 self.revisionExpr)
1132 elif branch.merge:
1133 syncbuf.info(self,
1134 'manifest no longer tracks %s',
1135 branch.merge)
1136
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001137 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001139 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001141 syncbuf.info(self,
1142 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001143 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001145 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001146 if not ID_RE.match(self.revisionExpr):
1147 # in case of manifest sync the revisionExpr might be a SHA1
1148 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 branch.Save()
1150
Mike Pontillod3153822012-02-28 11:53:24 -08001151 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001152 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 self._CopyFiles()
1155 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001156 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001158 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001159 self._CopyFiles()
1160 except GitError, e:
1161 syncbuf.fail(self, e)
1162 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001164 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001165 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 self._CopyFiles()
1167 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001169 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 # dest should already be an absolute path, but src is project relative
1171 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001172 abssrc = os.path.join(self.worktree, src)
1173 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174
James W. Mills24c13082012-04-12 15:04:13 -05001175 def AddAnnotation(self, name, value, keep):
1176 self.annotations.append(_Annotation(name, value, keep))
1177
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001178 def DownloadPatchSet(self, change_id, patch_id):
1179 """Download a single patch set of a single change to FETCH_HEAD.
1180 """
1181 remote = self.GetRemote(self.remote.name)
1182
1183 cmd = ['fetch', remote.name]
1184 cmd.append('refs/changes/%2.2d/%d/%d' \
1185 % (change_id % 100, change_id, patch_id))
1186 cmd.extend(map(lambda x: str(x), remote.fetch))
1187 if GitCommand(self, cmd, bare=True).Wait() != 0:
1188 return None
1189 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001191 change_id,
1192 patch_id,
1193 self.bare_git.rev_parse('FETCH_HEAD'))
1194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195
1196## Branch Management ##
1197
1198 def StartBranch(self, name):
1199 """Create a new branch off the manifest's revision.
1200 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001201 head = self.work_git.GetHead()
1202 if head == (R_HEADS + name):
1203 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001205 all = self.bare_ref.all
1206 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001207 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001208 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001209 capture_stdout = True,
1210 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001211
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001212 branch = self.GetBranch(name)
1213 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 branch.merge = self.revisionExpr
1215 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001216
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001217 if head.startswith(R_HEADS):
1218 try:
1219 head = all[head]
1220 except KeyError:
1221 head = None
1222
1223 if revid and head and revid == head:
1224 ref = os.path.join(self.gitdir, R_HEADS + name)
1225 try:
1226 os.makedirs(os.path.dirname(ref))
1227 except OSError:
1228 pass
1229 _lwrite(ref, '%s\n' % revid)
1230 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1231 'ref: %s%s\n' % (R_HEADS, name))
1232 branch.Save()
1233 return True
1234
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001235 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001237 capture_stdout = True,
1238 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001239 branch.Save()
1240 return True
1241 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Wink Saville02d79452009-04-10 13:01:24 -07001243 def CheckoutBranch(self, name):
1244 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001245
1246 Args:
1247 name: The name of the branch to checkout.
1248
1249 Returns:
1250 True if the checkout succeeded; False if it didn't; None if the branch
1251 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001252 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001253 rev = R_HEADS + name
1254 head = self.work_git.GetHead()
1255 if head == rev:
1256 # Already on the branch
1257 #
1258 return True
Wink Saville02d79452009-04-10 13:01:24 -07001259
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001261 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 revid = all[rev]
1263 except KeyError:
1264 # Branch does not exist in this project
1265 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001266 return None
Wink Saville02d79452009-04-10 13:01:24 -07001267
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001268 if head.startswith(R_HEADS):
1269 try:
1270 head = all[head]
1271 except KeyError:
1272 head = None
1273
1274 if head == revid:
1275 # Same revision; just update HEAD to point to the new
1276 # target branch, but otherwise take no other action.
1277 #
1278 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1279 'ref: %s%s\n' % (R_HEADS, name))
1280 return True
1281
1282 return GitCommand(self,
1283 ['checkout', name, '--'],
1284 capture_stdout = True,
1285 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001286
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001287 def AbandonBranch(self, name):
1288 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001289
1290 Args:
1291 name: The name of the branch to abandon.
1292
1293 Returns:
1294 True if the abandon succeeded; False if it didn't; None if the branch
1295 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001296 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001297 rev = R_HEADS + name
1298 all = self.bare_ref.all
1299 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001300 # Doesn't exist
1301 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001302
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001303 head = self.work_git.GetHead()
1304 if head == rev:
1305 # We can't destroy the branch while we are sitting
1306 # on it. Switch to a detached HEAD.
1307 #
1308 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001309
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 revid = self.GetRevisionId(all)
1311 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001312 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1313 '%s\n' % revid)
1314 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001315 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001316
1317 return GitCommand(self,
1318 ['branch', '-D', name],
1319 capture_stdout = True,
1320 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 def PruneHeads(self):
1323 """Prune any topic branches already merged into upstream.
1324 """
1325 cb = self.CurrentBranch
1326 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001327 left = self._allrefs
1328 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 if name.startswith(R_HEADS):
1330 name = name[len(R_HEADS):]
1331 if cb is None or name != cb:
1332 kill.append(name)
1333
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001334 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 if cb is not None \
1336 and not self._revlist(HEAD + '...' + rev) \
1337 and not self.IsDirty(consider_untracked = False):
1338 self.work_git.DetachHead(HEAD)
1339 kill.append(cb)
1340
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001342 old = self.bare_git.GetHead()
1343 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1345
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 try:
1347 self.bare_git.DetachHead(rev)
1348
1349 b = ['branch', '-d']
1350 b.extend(kill)
1351 b = GitCommand(self, b, bare=True,
1352 capture_stdout=True,
1353 capture_stderr=True)
1354 b.Wait()
1355 finally:
1356 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001357 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001359 for branch in kill:
1360 if (R_HEADS + branch) not in left:
1361 self.CleanPublishedCache()
1362 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
1364 if cb and cb not in kill:
1365 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001366 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367
1368 kept = []
1369 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001370 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 branch = self.GetBranch(branch)
1372 base = branch.LocalMerge
1373 if not base:
1374 base = rev
1375 kept.append(ReviewableBranch(self, branch, base))
1376 return kept
1377
1378
1379## Direct Git Commands ##
1380
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001381 def _RemoteFetch(self, name=None,
1382 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001383 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001384 quiet=False,
1385 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001386
1387 is_sha1 = False
1388 tag_name = None
1389
1390 if current_branch_only:
1391 if ID_RE.match(self.revisionExpr) is not None:
1392 is_sha1 = True
1393 elif self.revisionExpr.startswith(R_TAGS):
1394 # this is a tag and its sha1 value should never change
1395 tag_name = self.revisionExpr[len(R_TAGS):]
1396
1397 if is_sha1 or tag_name is not None:
1398 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001399 # if revision (sha or tag) is not present then following function
1400 # throws an error.
1401 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001402 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001403 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001404 # There is no such persistent revision. We have to fetch it.
1405 pass
1406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 if not name:
1408 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001409
1410 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001411 remote = self.GetRemote(name)
1412 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001413 ssh_proxy = True
1414
Shawn O. Pearce88443382010-10-08 10:02:09 +02001415 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001416 if alt_dir and 'objects' == os.path.basename(alt_dir):
1417 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001418 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1419 remote = self.GetRemote(name)
1420
1421 all = self.bare_ref.all
1422 ids = set(all.values())
1423 tmp = set()
1424
1425 for r, id in GitRefs(ref_dir).all.iteritems():
1426 if r not in all:
1427 if r.startswith(R_TAGS) or remote.WritesTo(r):
1428 all[r] = id
1429 ids.add(id)
1430 continue
1431
1432 if id in ids:
1433 continue
1434
1435 r = 'refs/_alt/%s' % id
1436 all[r] = id
1437 ids.add(id)
1438 tmp.add(r)
1439
1440 ref_names = list(all.keys())
1441 ref_names.sort()
1442
1443 tmp_packed = ''
1444 old_packed = ''
1445
1446 for r in ref_names:
1447 line = '%s %s\n' % (all[r], r)
1448 tmp_packed += line
1449 if r not in tmp:
1450 old_packed += line
1451
1452 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001453 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001454 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001455
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001456 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001457
1458 # The --depth option only affects the initial fetch; after that we'll do
1459 # full fetches of changes.
1460 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1461 if depth and initial:
1462 cmd.append('--depth=%s' % depth)
1463
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001464 if quiet:
1465 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001466 if not self.worktree:
1467 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001468 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001469
1470 if not current_branch_only or is_sha1:
1471 # Fetch whole repo
1472 cmd.append('--tags')
1473 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1474 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001475 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001476 cmd.append(tag_name)
1477 else:
1478 branch = self.revisionExpr
1479 if branch.startswith(R_HEADS):
1480 branch = branch[len(R_HEADS):]
1481 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001482
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 ok = False
1484 for i in range(2):
1485 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1486 ok = True
1487 break
1488 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001489
1490 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001491 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001492 if old_packed != '':
1493 _lwrite(packed_refs, old_packed)
1494 else:
1495 os.remove(packed_refs)
1496 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001497 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001498
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 def _ApplyCloneBundle(self, initial=False, quiet=False):
1500 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1501 return False
1502
1503 remote = self.GetRemote(self.remote.name)
1504 bundle_url = remote.url + '/clone.bundle'
1505 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001506 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1507 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001508 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1509 return False
1510
1511 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1512 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1513
1514 exist_dst = os.path.exists(bundle_dst)
1515 exist_tmp = os.path.exists(bundle_tmp)
1516
1517 if not initial and not exist_dst and not exist_tmp:
1518 return False
1519
1520 if not exist_dst:
1521 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1522 if not exist_dst:
1523 return False
1524
1525 cmd = ['fetch']
1526 if quiet:
1527 cmd.append('--quiet')
1528 if not self.worktree:
1529 cmd.append('--update-head-ok')
1530 cmd.append(bundle_dst)
1531 for f in remote.fetch:
1532 cmd.append(str(f))
1533 cmd.append('refs/tags/*:refs/tags/*')
1534
1535 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001536 if os.path.exists(bundle_dst):
1537 os.remove(bundle_dst)
1538 if os.path.exists(bundle_tmp):
1539 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001540 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001541
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001542 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001543 keep = True
1544 done = False
1545 dest = open(tmpPath, 'a+b')
1546 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001547 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001548 pos = dest.tell()
1549
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001550 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001551 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001552 req = urllib2.Request(srcUrl)
1553 if pos > 0:
1554 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001555
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001556 try:
1557 r = urllib2.urlopen(req)
1558 except urllib2.HTTPError, e:
1559 def _content_type():
1560 try:
1561 return e.info()['content-type']
1562 except:
1563 return None
1564
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001565 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001566 keep = False
1567 return False
1568 elif _content_type() == 'text/plain':
1569 try:
1570 msg = e.read()
1571 if len(msg) > 0 and msg[-1] == '\n':
1572 msg = msg[0:-1]
1573 msg = ' (%s)' % msg
1574 except:
1575 msg = ''
1576 else:
1577 try:
1578 from BaseHTTPServer import BaseHTTPRequestHandler
1579 res = BaseHTTPRequestHandler.responses[e.code]
1580 msg = ' (%s: %s)' % (res[0], res[1])
1581 except:
1582 msg = ''
1583 raise DownloadError('HTTP %s%s' % (e.code, msg))
1584 except urllib2.URLError, e:
1585 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1586 finally:
1587 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001588
1589 p = None
1590 try:
Conley Owens43bda842012-03-12 11:25:04 -07001591 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001592 unit = 1 << 10
1593
1594 if size and not quiet:
1595 if size > 1024 * 1.3:
1596 unit = 1 << 20
1597 desc = 'MB'
1598 else:
1599 desc = 'KB'
1600 p = Progress(
1601 'Downloading %s' % self.relpath,
1602 int(size) / unit,
1603 units=desc)
1604 if pos > 0:
1605 p.update(pos / unit)
1606
1607 s = 0
1608 while True:
1609 d = r.read(8192)
1610 if d == '':
1611 done = True
1612 return True
1613 dest.write(d)
1614 if p:
1615 s += len(d)
1616 if s >= unit:
1617 p.update(s / unit)
1618 s = s % unit
1619 if p:
1620 if s >= unit:
1621 p.update(s / unit)
1622 else:
1623 p.update(1)
1624 finally:
1625 r.close()
1626 if p:
1627 p.end()
1628 finally:
1629 dest.close()
1630
1631 if os.path.exists(dstPath):
1632 os.remove(dstPath)
1633 if done:
1634 os.rename(tmpPath, dstPath)
1635 elif not keep:
1636 os.remove(tmpPath)
1637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001638 def _Checkout(self, rev, quiet=False):
1639 cmd = ['checkout']
1640 if quiet:
1641 cmd.append('-q')
1642 cmd.append(rev)
1643 cmd.append('--')
1644 if GitCommand(self, cmd).Wait() != 0:
1645 if self._allrefs:
1646 raise GitError('%s checkout %s ' % (self.name, rev))
1647
Pierre Tardye5a21222011-03-24 16:28:18 +01001648 def _CherryPick(self, rev, quiet=False):
1649 cmd = ['cherry-pick']
1650 cmd.append(rev)
1651 cmd.append('--')
1652 if GitCommand(self, cmd).Wait() != 0:
1653 if self._allrefs:
1654 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1655
Erwan Mahea94f1622011-08-19 13:56:09 +02001656 def _Revert(self, rev, quiet=False):
1657 cmd = ['revert']
1658 cmd.append('--no-edit')
1659 cmd.append(rev)
1660 cmd.append('--')
1661 if GitCommand(self, cmd).Wait() != 0:
1662 if self._allrefs:
1663 raise GitError('%s revert %s ' % (self.name, rev))
1664
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665 def _ResetHard(self, rev, quiet=True):
1666 cmd = ['reset', '--hard']
1667 if quiet:
1668 cmd.append('-q')
1669 cmd.append(rev)
1670 if GitCommand(self, cmd).Wait() != 0:
1671 raise GitError('%s reset --hard %s ' % (self.name, rev))
1672
1673 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001674 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001675 if onto is not None:
1676 cmd.extend(['--onto', onto])
1677 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001678 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001679 raise GitError('%s rebase %s ' % (self.name, upstream))
1680
Pierre Tardy3d125942012-05-04 12:18:12 +02001681 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001683 if ffonly:
1684 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 if GitCommand(self, cmd).Wait() != 0:
1686 raise GitError('%s merge %s ' % (self.name, head))
1687
1688 def _InitGitDir(self):
1689 if not os.path.exists(self.gitdir):
1690 os.makedirs(self.gitdir)
1691 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001692
Shawn O. Pearce88443382010-10-08 10:02:09 +02001693 mp = self.manifest.manifestProject
1694 ref_dir = mp.config.GetString('repo.reference')
1695
1696 if ref_dir:
1697 mirror_git = os.path.join(ref_dir, self.name + '.git')
1698 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1699 self.relpath + '.git')
1700
1701 if os.path.exists(mirror_git):
1702 ref_dir = mirror_git
1703
1704 elif os.path.exists(repo_git):
1705 ref_dir = repo_git
1706
1707 else:
1708 ref_dir = None
1709
1710 if ref_dir:
1711 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1712 os.path.join(ref_dir, 'objects') + '\n')
1713
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001714 if self.manifest.IsMirror:
1715 self.config.SetString('core.bare', 'true')
1716 else:
1717 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718
1719 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001720 try:
1721 to_rm = os.listdir(hooks)
1722 except OSError:
1723 to_rm = []
1724 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001726 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
1728 m = self.manifest.manifestProject.config
1729 for key in ['user.name', 'user.email']:
1730 if m.Has(key, include_defaults = False):
1731 self.config.SetString(key, m.GetString(key))
1732
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001733 def _InitHooks(self):
1734 hooks = self._gitdir_path('hooks')
1735 if not os.path.exists(hooks):
1736 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001737 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001738 name = os.path.basename(stock_hook)
1739
Victor Boivie65e0f352011-04-18 11:23:29 +02001740 if name in ('commit-msg',) and not self.remote.review \
1741 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001742 # Don't install a Gerrit Code Review hook if this
1743 # project does not appear to use it for reviews.
1744 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001745 # Since the manifest project is one of those, but also
1746 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001747 continue
1748
1749 dst = os.path.join(hooks, name)
1750 if os.path.islink(dst):
1751 continue
1752 if os.path.exists(dst):
1753 if filecmp.cmp(stock_hook, dst, shallow=False):
1754 os.remove(dst)
1755 else:
1756 _error("%s: Not replacing %s hook", self.relpath, name)
1757 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001758 try:
1759 os.symlink(relpath(stock_hook, dst), dst)
1760 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001761 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001762 raise GitError('filesystem must support symlinks')
1763 else:
1764 raise
1765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001767 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001769 remote.url = self.remote.url
1770 remote.review = self.remote.review
1771 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001773 if self.worktree:
1774 remote.ResetFetch(mirror=False)
1775 else:
1776 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777 remote.Save()
1778
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001779 def _InitMRef(self):
1780 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001781 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001783 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001784 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001785
1786 def _InitAnyMRef(self, ref):
1787 cur = self.bare_ref.symref(ref)
1788
1789 if self.revisionId:
1790 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1791 msg = 'manifest set to %s' % self.revisionId
1792 dst = self.revisionId + '^0'
1793 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1794 else:
1795 remote = self.GetRemote(self.remote.name)
1796 dst = remote.ToLocal(self.revisionExpr)
1797 if cur != dst:
1798 msg = 'manifest set to %s' % self.revisionExpr
1799 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001800
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801 def _InitWorkTree(self):
1802 dotgit = os.path.join(self.worktree, '.git')
1803 if not os.path.exists(dotgit):
1804 os.makedirs(dotgit)
1805
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001806 for name in ['config',
1807 'description',
1808 'hooks',
1809 'info',
1810 'logs',
1811 'objects',
1812 'packed-refs',
1813 'refs',
1814 'rr-cache',
1815 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001816 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001817 src = os.path.join(self.gitdir, name)
1818 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001819 if os.path.islink(dst) or not os.path.exists(dst):
1820 os.symlink(relpath(src, dst), dst)
1821 else:
1822 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001823 except OSError, e:
1824 if e.errno == errno.EPERM:
1825 raise GitError('filesystem must support symlinks')
1826 else:
1827 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001829 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830
1831 cmd = ['read-tree', '--reset', '-u']
1832 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001833 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834 if GitCommand(self, cmd).Wait() != 0:
1835 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001836
1837 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1838 if not os.path.exists(rr_cache):
1839 os.makedirs(rr_cache)
1840
Shawn O. Pearce93609662009-04-21 10:50:33 -07001841 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842
1843 def _gitdir_path(self, path):
1844 return os.path.join(self.gitdir, path)
1845
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001846 def _revlist(self, *args, **kw):
1847 a = []
1848 a.extend(args)
1849 a.append('--')
1850 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001851
1852 @property
1853 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001854 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855
1856 class _GitGetByExec(object):
1857 def __init__(self, project, bare):
1858 self._project = project
1859 self._bare = bare
1860
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861 def LsOthers(self):
1862 p = GitCommand(self._project,
1863 ['ls-files',
1864 '-z',
1865 '--others',
1866 '--exclude-standard'],
1867 bare = False,
1868 capture_stdout = True,
1869 capture_stderr = True)
1870 if p.Wait() == 0:
1871 out = p.stdout
1872 if out:
1873 return out[:-1].split("\0")
1874 return []
1875
1876 def DiffZ(self, name, *args):
1877 cmd = [name]
1878 cmd.append('-z')
1879 cmd.extend(args)
1880 p = GitCommand(self._project,
1881 cmd,
1882 bare = False,
1883 capture_stdout = True,
1884 capture_stderr = True)
1885 try:
1886 out = p.process.stdout.read()
1887 r = {}
1888 if out:
1889 out = iter(out[:-1].split('\0'))
1890 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001891 try:
1892 info = out.next()
1893 path = out.next()
1894 except StopIteration:
1895 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001896
1897 class _Info(object):
1898 def __init__(self, path, omode, nmode, oid, nid, state):
1899 self.path = path
1900 self.src_path = None
1901 self.old_mode = omode
1902 self.new_mode = nmode
1903 self.old_id = oid
1904 self.new_id = nid
1905
1906 if len(state) == 1:
1907 self.status = state
1908 self.level = None
1909 else:
1910 self.status = state[:1]
1911 self.level = state[1:]
1912 while self.level.startswith('0'):
1913 self.level = self.level[1:]
1914
1915 info = info[1:].split(' ')
1916 info =_Info(path, *info)
1917 if info.status in ('R', 'C'):
1918 info.src_path = info.path
1919 info.path = out.next()
1920 r[info.path] = info
1921 return r
1922 finally:
1923 p.Wait()
1924
1925 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001926 if self._bare:
1927 path = os.path.join(self._project.gitdir, HEAD)
1928 else:
1929 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001930 fd = open(path, 'rb')
1931 try:
1932 line = fd.read()
1933 finally:
1934 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001935 if line.startswith('ref: '):
1936 return line[5:-1]
1937 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938
1939 def SetHead(self, ref, message=None):
1940 cmdv = []
1941 if message is not None:
1942 cmdv.extend(['-m', message])
1943 cmdv.append(HEAD)
1944 cmdv.append(ref)
1945 self.symbolic_ref(*cmdv)
1946
1947 def DetachHead(self, new, message=None):
1948 cmdv = ['--no-deref']
1949 if message is not None:
1950 cmdv.extend(['-m', message])
1951 cmdv.append(HEAD)
1952 cmdv.append(new)
1953 self.update_ref(*cmdv)
1954
1955 def UpdateRef(self, name, new, old=None,
1956 message=None,
1957 detach=False):
1958 cmdv = []
1959 if message is not None:
1960 cmdv.extend(['-m', message])
1961 if detach:
1962 cmdv.append('--no-deref')
1963 cmdv.append(name)
1964 cmdv.append(new)
1965 if old is not None:
1966 cmdv.append(old)
1967 self.update_ref(*cmdv)
1968
1969 def DeleteRef(self, name, old=None):
1970 if not old:
1971 old = self.rev_parse(name)
1972 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001973 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001974
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001975 def rev_list(self, *args, **kw):
1976 if 'format' in kw:
1977 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1978 else:
1979 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001980 cmdv.extend(args)
1981 p = GitCommand(self._project,
1982 cmdv,
1983 bare = self._bare,
1984 capture_stdout = True,
1985 capture_stderr = True)
1986 r = []
1987 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001988 if line[-1] == '\n':
1989 line = line[:-1]
1990 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001991 if p.Wait() != 0:
1992 raise GitError('%s rev-list %s: %s' % (
1993 self._project.name,
1994 str(args),
1995 p.stderr))
1996 return r
1997
1998 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001999 """Allow arbitrary git commands using pythonic syntax.
2000
2001 This allows you to do things like:
2002 git_obj.rev_parse('HEAD')
2003
2004 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2005 run. We'll replace the '_' with a '-' and try to run a git command.
2006 Any other arguments will be passed to the git command.
2007
2008 Args:
2009 name: The name of the git command to call. Any '_' characters will
2010 be replaced with '-'.
2011
2012 Returns:
2013 A callable object that will try to call git with the named command.
2014 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002015 name = name.replace('_', '-')
2016 def runner(*args):
2017 cmdv = [name]
2018 cmdv.extend(args)
2019 p = GitCommand(self._project,
2020 cmdv,
2021 bare = self._bare,
2022 capture_stdout = True,
2023 capture_stderr = True)
2024 if p.Wait() != 0:
2025 raise GitError('%s %s: %s' % (
2026 self._project.name,
2027 name,
2028 p.stderr))
2029 r = p.stdout
2030 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2031 return r[:-1]
2032 return r
2033 return runner
2034
2035
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002036class _PriorSyncFailedError(Exception):
2037 def __str__(self):
2038 return 'prior sync failed; rebase still in progress'
2039
2040class _DirtyError(Exception):
2041 def __str__(self):
2042 return 'contains uncommitted changes'
2043
2044class _InfoMessage(object):
2045 def __init__(self, project, text):
2046 self.project = project
2047 self.text = text
2048
2049 def Print(self, syncbuf):
2050 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2051 syncbuf.out.nl()
2052
2053class _Failure(object):
2054 def __init__(self, project, why):
2055 self.project = project
2056 self.why = why
2057
2058 def Print(self, syncbuf):
2059 syncbuf.out.fail('error: %s/: %s',
2060 self.project.relpath,
2061 str(self.why))
2062 syncbuf.out.nl()
2063
2064class _Later(object):
2065 def __init__(self, project, action):
2066 self.project = project
2067 self.action = action
2068
2069 def Run(self, syncbuf):
2070 out = syncbuf.out
2071 out.project('project %s/', self.project.relpath)
2072 out.nl()
2073 try:
2074 self.action()
2075 out.nl()
2076 return True
2077 except GitError, e:
2078 out.nl()
2079 return False
2080
2081class _SyncColoring(Coloring):
2082 def __init__(self, config):
2083 Coloring.__init__(self, config, 'reposync')
2084 self.project = self.printer('header', attr = 'bold')
2085 self.info = self.printer('info')
2086 self.fail = self.printer('fail', fg='red')
2087
2088class SyncBuffer(object):
2089 def __init__(self, config, detach_head=False):
2090 self._messages = []
2091 self._failures = []
2092 self._later_queue1 = []
2093 self._later_queue2 = []
2094
2095 self.out = _SyncColoring(config)
2096 self.out.redirect(sys.stderr)
2097
2098 self.detach_head = detach_head
2099 self.clean = True
2100
2101 def info(self, project, fmt, *args):
2102 self._messages.append(_InfoMessage(project, fmt % args))
2103
2104 def fail(self, project, err=None):
2105 self._failures.append(_Failure(project, err))
2106 self.clean = False
2107
2108 def later1(self, project, what):
2109 self._later_queue1.append(_Later(project, what))
2110
2111 def later2(self, project, what):
2112 self._later_queue2.append(_Later(project, what))
2113
2114 def Finish(self):
2115 self._PrintMessages()
2116 self._RunLater()
2117 self._PrintMessages()
2118 return self.clean
2119
2120 def _RunLater(self):
2121 for q in ['_later_queue1', '_later_queue2']:
2122 if not self._RunQueue(q):
2123 return
2124
2125 def _RunQueue(self, queue):
2126 for m in getattr(self, queue):
2127 if not m.Run(self):
2128 self.clean = False
2129 return False
2130 setattr(self, queue, [])
2131 return True
2132
2133 def _PrintMessages(self):
2134 for m in self._messages:
2135 m.Print(self)
2136 for m in self._failures:
2137 m.Print(self)
2138
2139 self._messages = []
2140 self._failures = []
2141
2142
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002143class MetaProject(Project):
2144 """A special project housed under .repo.
2145 """
2146 def __init__(self, manifest, name, gitdir, worktree):
2147 repodir = manifest.repodir
2148 Project.__init__(self,
2149 manifest = manifest,
2150 name = name,
2151 gitdir = gitdir,
2152 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002153 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002154 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002155 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002156 revisionId = None,
2157 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158
2159 def PreSync(self):
2160 if self.Exists:
2161 cb = self.CurrentBranch
2162 if cb:
2163 base = self.GetBranch(cb).merge
2164 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002165 self.revisionExpr = base
2166 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002167
2168 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002169 def LastFetch(self):
2170 try:
2171 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2172 return os.path.getmtime(fh)
2173 except OSError:
2174 return 0
2175
2176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002177 def HasChanges(self):
2178 """Has the remote received new commits not yet checked out?
2179 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002180 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002181 return False
2182
2183 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002184 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002185 head = self.work_git.GetHead()
2186 if head.startswith(R_HEADS):
2187 try:
2188 head = all[head]
2189 except KeyError:
2190 head = None
2191
2192 if revid == head:
2193 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002194 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002195 return True
2196 return False