blob: 66a123d258ae8215720b1a0c9e9f1709f84c7931 [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 """
Colin Crosseca119e2012-05-24 15:39:14 -0700669 if self.groups is None:
670 return True
Conley Owens971de8e2012-04-16 10:36:08 -0700671 matched = False
672 for group in manifest_groups:
673 if group.startswith('-') and group[1:] in self.groups:
674 matched = False
675 elif group in self.groups:
676 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700677
Conley Owens971de8e2012-04-16 10:36:08 -0700678 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679
680## Status Display ##
681
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500682 def HasChanges(self):
683 """Returns true if there are uncommitted changes.
684 """
685 self.work_git.update_index('-q',
686 '--unmerged',
687 '--ignore-missing',
688 '--refresh')
689 if self.IsRebaseInProgress():
690 return True
691
692 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
693 return True
694
695 if self.work_git.DiffZ('diff-files'):
696 return True
697
698 if self.work_git.LsOthers():
699 return True
700
701 return False
702
Terence Haddock4655e812011-03-31 12:33:34 +0200703 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200705
706 Args:
707 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 """
709 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200710 if output_redir == None:
711 output_redir = sys.stdout
712 print >>output_redir, ''
713 print >>output_redir, 'project %s/' % self.relpath
714 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715 return
716
717 self.work_git.update_index('-q',
718 '--unmerged',
719 '--ignore-missing',
720 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700721 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
723 df = self.work_git.DiffZ('diff-files')
724 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100725 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700726 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727
728 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200729 if not output_redir == None:
730 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731 out.project('project %-40s', self.relpath + '/')
732
733 branch = self.CurrentBranch
734 if branch is None:
735 out.nobranch('(*** NO BRANCH ***)')
736 else:
737 out.branch('branch %s', branch)
738 out.nl()
739
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700740 if rb:
741 out.important('prior sync failed; rebase still in progress')
742 out.nl()
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 paths = list()
745 paths.extend(di.keys())
746 paths.extend(df.keys())
747 paths.extend(do)
748
749 paths = list(set(paths))
750 paths.sort()
751
752 for p in paths:
753 try: i = di[p]
754 except KeyError: i = None
755
756 try: f = df[p]
757 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 if i: i_status = i.status.upper()
760 else: i_status = '-'
761
762 if f: f_status = f.status.lower()
763 else: f_status = '-'
764
765 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800766 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 i.src_path, p, i.level)
768 else:
769 line = ' %s%s\t%s' % (i_status, f_status, p)
770
771 if i and not f:
772 out.added('%s', line)
773 elif (i and f) or (not i and f):
774 out.changed('%s', line)
775 elif not i and not f:
776 out.untracked('%s', line)
777 else:
778 out.write('%s', line)
779 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200780
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700781 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782
pelyad67872d2012-03-28 14:49:58 +0300783 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 """Prints the status of the repository to stdout.
785 """
786 out = DiffColoring(self.config)
787 cmd = ['diff']
788 if out.is_on:
789 cmd.append('--color')
790 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300791 if absolute_paths:
792 cmd.append('--src-prefix=a/%s/' % self.relpath)
793 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794 cmd.append('--')
795 p = GitCommand(self,
796 cmd,
797 capture_stdout = True,
798 capture_stderr = True)
799 has_diff = False
800 for line in p.process.stdout:
801 if not has_diff:
802 out.nl()
803 out.project('project %s/' % self.relpath)
804 out.nl()
805 has_diff = True
806 print line[:-1]
807 p.Wait()
808
809
810## Publish / Upload ##
811
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700812 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 """Was the branch published (uploaded) for code review?
814 If so, returns the SHA-1 hash of the last published
815 state for the branch.
816 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700817 key = R_PUB + branch
818 if all is None:
819 try:
820 return self.bare_git.rev_parse(key)
821 except GitError:
822 return None
823 else:
824 try:
825 return all[key]
826 except KeyError:
827 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700829 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 """Prunes any stale published refs.
831 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700832 if all is None:
833 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 heads = set()
835 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700836 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 if name.startswith(R_HEADS):
838 heads.add(name)
839 elif name.startswith(R_PUB):
840 canrm[name] = id
841
842 for name, id in canrm.iteritems():
843 n = name[len(R_PUB):]
844 if R_HEADS + n not in heads:
845 self.bare_git.DeleteRef(name, id)
846
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700847 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 """List any branches which can be uploaded for review.
849 """
850 heads = {}
851 pubed = {}
852
853 for name, id in self._allrefs.iteritems():
854 if name.startswith(R_HEADS):
855 heads[name[len(R_HEADS):]] = id
856 elif name.startswith(R_PUB):
857 pubed[name[len(R_PUB):]] = id
858
859 ready = []
860 for branch, id in heads.iteritems():
861 if branch in pubed and pubed[branch] == id:
862 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700863 if selected_branch and branch != selected_branch:
864 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800866 rb = self.GetUploadableBranch(branch)
867 if rb:
868 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 return ready
870
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800871 def GetUploadableBranch(self, branch_name):
872 """Get a single uploadable branch, or None.
873 """
874 branch = self.GetBranch(branch_name)
875 base = branch.LocalMerge
876 if branch.LocalMerge:
877 rb = ReviewableBranch(self, branch, base)
878 if rb.commits:
879 return rb
880 return None
881
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700882 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700883 people=([],[]),
884 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 """Uploads the named branch for code review.
886 """
887 if branch is None:
888 branch = self.CurrentBranch
889 if branch is None:
890 raise GitError('not currently on a branch')
891
892 branch = self.GetBranch(branch)
893 if not branch.LocalMerge:
894 raise GitError('branch %s does not track a remote' % branch.name)
895 if not branch.remote.review:
896 raise GitError('remote %s has no review url' % branch.remote.name)
897
898 dest_branch = branch.merge
899 if not dest_branch.startswith(R_HEADS):
900 dest_branch = R_HEADS + dest_branch
901
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800902 if not branch.remote.projectname:
903 branch.remote.projectname = self.name
904 branch.remote.Save()
905
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800906 url = branch.remote.ReviewUrl(self.UserEmail)
907 if url is None:
908 raise UploadError('review not configured')
909 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800910
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800911 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800912 rp = ['gerrit receive-pack']
913 for e in people[0]:
914 rp.append('--reviewer=%s' % sq(e))
915 for e in people[1]:
916 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800917 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700918
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800919 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800920
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800921 if dest_branch.startswith(R_HEADS):
922 dest_branch = dest_branch[len(R_HEADS):]
923 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
924 if auto_topic:
925 ref_spec = ref_spec + '/' + branch.name
926 cmd.append(ref_spec)
927
928 if GitCommand(self, cmd, bare = True).Wait() != 0:
929 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930
931 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
932 self.bare_git.UpdateRef(R_PUB + branch.name,
933 R_HEADS + branch.name,
934 message = msg)
935
936
937## Sync ##
938
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700939 def Sync_NetworkHalf(self,
940 quiet=False,
941 is_new=None,
942 current_branch_only=False,
943 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 """Perform only the network IO portion of the sync process.
945 Local working directory/branch state is not affected.
946 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700947 if is_new is None:
948 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200949 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 self._InitGitDir()
951 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700952
953 if is_new:
954 alt = os.path.join(self.gitdir, 'objects/info/alternates')
955 try:
956 fd = open(alt, 'rb')
957 try:
958 alt_dir = fd.readline().rstrip()
959 finally:
960 fd.close()
961 except IOError:
962 alt_dir = None
963 else:
964 alt_dir = None
965
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700966 if clone_bundle \
967 and alt_dir is None \
968 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700969 is_new = False
970
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700971 if not current_branch_only:
972 if self.sync_c:
973 current_branch_only = True
974 elif not self.manifest._loaded:
975 # Manifest cannot check defaults until it syncs.
976 current_branch_only = False
977 elif self.manifest.default.sync_c:
978 current_branch_only = True
979
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700980 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
981 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800983
984 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800985 self._InitMRef()
986 else:
987 self._InitMirrorHead()
988 try:
989 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
990 except OSError:
991 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800993
994 def PostRepoUpgrade(self):
995 self._InitHooks()
996
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 def _CopyFiles(self):
998 for file in self.copyfiles:
999 file._Copy()
1000
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001001 def GetRevisionId(self, all=None):
1002 if self.revisionId:
1003 return self.revisionId
1004
1005 rem = self.GetRemote(self.remote.name)
1006 rev = rem.ToLocal(self.revisionExpr)
1007
1008 if all is not None and rev in all:
1009 return all[rev]
1010
1011 try:
1012 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1013 except GitError:
1014 raise ManifestInvalidRevisionError(
1015 'revision %s in %s not found' % (self.revisionExpr,
1016 self.name))
1017
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001018 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 """Perform only the local IO portion of the sync process.
1020 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001021 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001022 all = self.bare_ref.all
1023 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001024 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001025
1026 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001027 head = self.work_git.GetHead()
1028 if head.startswith(R_HEADS):
1029 branch = head[len(R_HEADS):]
1030 try:
1031 head = all[head]
1032 except KeyError:
1033 head = None
1034 else:
1035 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001037 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 # Currently on a detached HEAD. The user is assumed to
1039 # not have any local modifications worth worrying about.
1040 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001041 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001042 syncbuf.fail(self, _PriorSyncFailedError())
1043 return
1044
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 if head == revid:
1046 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001047 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001048 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001049 if not syncbuf.detach_head:
1050 return
1051 else:
1052 lost = self._revlist(not_rev(revid), HEAD)
1053 if lost:
1054 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001055
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001057 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001058 except GitError, e:
1059 syncbuf.fail(self, e)
1060 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001062 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001064 if head == revid:
1065 # No changes; don't do anything further.
1066 #
1067 return
1068
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001071 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001073 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001075 syncbuf.info(self,
1076 "leaving %s; does not track upstream",
1077 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001079 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 except GitError, e:
1081 syncbuf.fail(self, e)
1082 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001087 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001089 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 if not_merged:
1091 if upstream_gain:
1092 # The user has published this branch and some of those
1093 # commits are not yet merged upstream. We do not want
1094 # to rewrite the published commits so we punt.
1095 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001096 syncbuf.fail(self,
1097 "branch %s is published (but not merged) and is now %d commits behind"
1098 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001099 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001100 elif pub == head:
1101 # All published commits are merged, and thus we are a
1102 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001103 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001105 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001106 self._CopyFiles()
1107 syncbuf.later1(self, _doff)
1108 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001110 # Examine the local commits not in the remote. Find the
1111 # last one attributed to this user, if any.
1112 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001114 last_mine = None
1115 cnt_mine = 0
1116 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001117 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001118 if committer_email == self.UserEmail:
1119 last_mine = commit_id
1120 cnt_mine += 1
1121
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001122 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001123 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124
1125 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 syncbuf.fail(self, _DirtyError())
1127 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001129 # If the upstream switched on us, warn the user.
1130 #
1131 if branch.merge != self.revisionExpr:
1132 if branch.merge and self.revisionExpr:
1133 syncbuf.info(self,
1134 'manifest switched %s...%s',
1135 branch.merge,
1136 self.revisionExpr)
1137 elif branch.merge:
1138 syncbuf.info(self,
1139 'manifest no longer tracks %s',
1140 branch.merge)
1141
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001142 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001144 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001145 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001146 syncbuf.info(self,
1147 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001148 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001151 if not ID_RE.match(self.revisionExpr):
1152 # in case of manifest sync the revisionExpr might be a SHA1
1153 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154 branch.Save()
1155
Mike Pontillod3153822012-02-28 11:53:24 -08001156 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001157 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001158 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001159 self._CopyFiles()
1160 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001161 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001164 self._CopyFiles()
1165 except GitError, e:
1166 syncbuf.fail(self, e)
1167 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001169 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001170 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001171 self._CopyFiles()
1172 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001174 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175 # dest should already be an absolute path, but src is project relative
1176 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001177 abssrc = os.path.join(self.worktree, src)
1178 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
James W. Mills24c13082012-04-12 15:04:13 -05001180 def AddAnnotation(self, name, value, keep):
1181 self.annotations.append(_Annotation(name, value, keep))
1182
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001183 def DownloadPatchSet(self, change_id, patch_id):
1184 """Download a single patch set of a single change to FETCH_HEAD.
1185 """
1186 remote = self.GetRemote(self.remote.name)
1187
1188 cmd = ['fetch', remote.name]
1189 cmd.append('refs/changes/%2.2d/%d/%d' \
1190 % (change_id % 100, change_id, patch_id))
1191 cmd.extend(map(lambda x: str(x), remote.fetch))
1192 if GitCommand(self, cmd, bare=True).Wait() != 0:
1193 return None
1194 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001195 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001196 change_id,
1197 patch_id,
1198 self.bare_git.rev_parse('FETCH_HEAD'))
1199
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
1201## Branch Management ##
1202
1203 def StartBranch(self, name):
1204 """Create a new branch off the manifest's revision.
1205 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001206 head = self.work_git.GetHead()
1207 if head == (R_HEADS + name):
1208 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001210 all = self.bare_ref.all
1211 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001212 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001213 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001214 capture_stdout = True,
1215 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001216
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001217 branch = self.GetBranch(name)
1218 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001219 branch.merge = self.revisionExpr
1220 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001221
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001222 if head.startswith(R_HEADS):
1223 try:
1224 head = all[head]
1225 except KeyError:
1226 head = None
1227
1228 if revid and head and revid == head:
1229 ref = os.path.join(self.gitdir, R_HEADS + name)
1230 try:
1231 os.makedirs(os.path.dirname(ref))
1232 except OSError:
1233 pass
1234 _lwrite(ref, '%s\n' % revid)
1235 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1236 'ref: %s%s\n' % (R_HEADS, name))
1237 branch.Save()
1238 return True
1239
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001240 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001241 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001242 capture_stdout = True,
1243 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001244 branch.Save()
1245 return True
1246 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247
Wink Saville02d79452009-04-10 13:01:24 -07001248 def CheckoutBranch(self, name):
1249 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001250
1251 Args:
1252 name: The name of the branch to checkout.
1253
1254 Returns:
1255 True if the checkout succeeded; False if it didn't; None if the branch
1256 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001257 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001258 rev = R_HEADS + name
1259 head = self.work_git.GetHead()
1260 if head == rev:
1261 # Already on the branch
1262 #
1263 return True
Wink Saville02d79452009-04-10 13:01:24 -07001264
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001265 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001266 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001267 revid = all[rev]
1268 except KeyError:
1269 # Branch does not exist in this project
1270 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001271 return None
Wink Saville02d79452009-04-10 13:01:24 -07001272
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001273 if head.startswith(R_HEADS):
1274 try:
1275 head = all[head]
1276 except KeyError:
1277 head = None
1278
1279 if head == revid:
1280 # Same revision; just update HEAD to point to the new
1281 # target branch, but otherwise take no other action.
1282 #
1283 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1284 'ref: %s%s\n' % (R_HEADS, name))
1285 return True
1286
1287 return GitCommand(self,
1288 ['checkout', name, '--'],
1289 capture_stdout = True,
1290 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001291
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001292 def AbandonBranch(self, name):
1293 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001294
1295 Args:
1296 name: The name of the branch to abandon.
1297
1298 Returns:
1299 True if the abandon succeeded; False if it didn't; None if the branch
1300 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001301 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001302 rev = R_HEADS + name
1303 all = self.bare_ref.all
1304 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001305 # Doesn't exist
1306 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001307
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001308 head = self.work_git.GetHead()
1309 if head == rev:
1310 # We can't destroy the branch while we are sitting
1311 # on it. Switch to a detached HEAD.
1312 #
1313 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001314
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001315 revid = self.GetRevisionId(all)
1316 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001317 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1318 '%s\n' % revid)
1319 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001320 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001321
1322 return GitCommand(self,
1323 ['branch', '-D', name],
1324 capture_stdout = True,
1325 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 def PruneHeads(self):
1328 """Prune any topic branches already merged into upstream.
1329 """
1330 cb = self.CurrentBranch
1331 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001332 left = self._allrefs
1333 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334 if name.startswith(R_HEADS):
1335 name = name[len(R_HEADS):]
1336 if cb is None or name != cb:
1337 kill.append(name)
1338
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001339 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 if cb is not None \
1341 and not self._revlist(HEAD + '...' + rev) \
1342 and not self.IsDirty(consider_untracked = False):
1343 self.work_git.DetachHead(HEAD)
1344 kill.append(cb)
1345
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001347 old = self.bare_git.GetHead()
1348 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1350
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351 try:
1352 self.bare_git.DetachHead(rev)
1353
1354 b = ['branch', '-d']
1355 b.extend(kill)
1356 b = GitCommand(self, b, bare=True,
1357 capture_stdout=True,
1358 capture_stderr=True)
1359 b.Wait()
1360 finally:
1361 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001362 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001364 for branch in kill:
1365 if (R_HEADS + branch) not in left:
1366 self.CleanPublishedCache()
1367 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
1369 if cb and cb not in kill:
1370 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001371 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372
1373 kept = []
1374 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001375 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 branch = self.GetBranch(branch)
1377 base = branch.LocalMerge
1378 if not base:
1379 base = rev
1380 kept.append(ReviewableBranch(self, branch, base))
1381 return kept
1382
1383
1384## Direct Git Commands ##
1385
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001386 def _RemoteFetch(self, name=None,
1387 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001388 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001389 quiet=False,
1390 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001391
1392 is_sha1 = False
1393 tag_name = None
1394
1395 if current_branch_only:
1396 if ID_RE.match(self.revisionExpr) is not None:
1397 is_sha1 = True
1398 elif self.revisionExpr.startswith(R_TAGS):
1399 # this is a tag and its sha1 value should never change
1400 tag_name = self.revisionExpr[len(R_TAGS):]
1401
1402 if is_sha1 or tag_name is not None:
1403 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001404 # if revision (sha or tag) is not present then following function
1405 # throws an error.
1406 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001407 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001408 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001409 # There is no such persistent revision. We have to fetch it.
1410 pass
1411
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412 if not name:
1413 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001414
1415 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001416 remote = self.GetRemote(name)
1417 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001418 ssh_proxy = True
1419
Shawn O. Pearce88443382010-10-08 10:02:09 +02001420 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001421 if alt_dir and 'objects' == os.path.basename(alt_dir):
1422 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001423 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1424 remote = self.GetRemote(name)
1425
1426 all = self.bare_ref.all
1427 ids = set(all.values())
1428 tmp = set()
1429
1430 for r, id in GitRefs(ref_dir).all.iteritems():
1431 if r not in all:
1432 if r.startswith(R_TAGS) or remote.WritesTo(r):
1433 all[r] = id
1434 ids.add(id)
1435 continue
1436
1437 if id in ids:
1438 continue
1439
1440 r = 'refs/_alt/%s' % id
1441 all[r] = id
1442 ids.add(id)
1443 tmp.add(r)
1444
1445 ref_names = list(all.keys())
1446 ref_names.sort()
1447
1448 tmp_packed = ''
1449 old_packed = ''
1450
1451 for r in ref_names:
1452 line = '%s %s\n' % (all[r], r)
1453 tmp_packed += line
1454 if r not in tmp:
1455 old_packed += line
1456
1457 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001458 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001459 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001460
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001461 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001462
1463 # The --depth option only affects the initial fetch; after that we'll do
1464 # full fetches of changes.
1465 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1466 if depth and initial:
1467 cmd.append('--depth=%s' % depth)
1468
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001469 if quiet:
1470 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001471 if not self.worktree:
1472 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001473 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001474
1475 if not current_branch_only or is_sha1:
1476 # Fetch whole repo
1477 cmd.append('--tags')
1478 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1479 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001480 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001481 cmd.append(tag_name)
1482 else:
1483 branch = self.revisionExpr
1484 if branch.startswith(R_HEADS):
1485 branch = branch[len(R_HEADS):]
1486 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001487
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001488 ok = False
1489 for i in range(2):
1490 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1491 ok = True
1492 break
1493 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001494
1495 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001496 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001497 if old_packed != '':
1498 _lwrite(packed_refs, old_packed)
1499 else:
1500 os.remove(packed_refs)
1501 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001502 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001503
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001504 def _ApplyCloneBundle(self, initial=False, quiet=False):
1505 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1506 return False
1507
1508 remote = self.GetRemote(self.remote.name)
1509 bundle_url = remote.url + '/clone.bundle'
1510 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001511 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1512 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001513 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1514 return False
1515
1516 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1517 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1518
1519 exist_dst = os.path.exists(bundle_dst)
1520 exist_tmp = os.path.exists(bundle_tmp)
1521
1522 if not initial and not exist_dst and not exist_tmp:
1523 return False
1524
1525 if not exist_dst:
1526 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1527 if not exist_dst:
1528 return False
1529
1530 cmd = ['fetch']
1531 if quiet:
1532 cmd.append('--quiet')
1533 if not self.worktree:
1534 cmd.append('--update-head-ok')
1535 cmd.append(bundle_dst)
1536 for f in remote.fetch:
1537 cmd.append(str(f))
1538 cmd.append('refs/tags/*:refs/tags/*')
1539
1540 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001541 if os.path.exists(bundle_dst):
1542 os.remove(bundle_dst)
1543 if os.path.exists(bundle_tmp):
1544 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001545 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001546
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001547 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001548 keep = True
1549 done = False
1550 dest = open(tmpPath, 'a+b')
1551 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001552 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001553 pos = dest.tell()
1554
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001555 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001556 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001557 req = urllib2.Request(srcUrl)
1558 if pos > 0:
1559 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001560
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001561 try:
1562 r = urllib2.urlopen(req)
1563 except urllib2.HTTPError, e:
1564 def _content_type():
1565 try:
1566 return e.info()['content-type']
1567 except:
1568 return None
1569
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001570 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001571 keep = False
1572 return False
1573 elif _content_type() == 'text/plain':
1574 try:
1575 msg = e.read()
1576 if len(msg) > 0 and msg[-1] == '\n':
1577 msg = msg[0:-1]
1578 msg = ' (%s)' % msg
1579 except:
1580 msg = ''
1581 else:
1582 try:
1583 from BaseHTTPServer import BaseHTTPRequestHandler
1584 res = BaseHTTPRequestHandler.responses[e.code]
1585 msg = ' (%s: %s)' % (res[0], res[1])
1586 except:
1587 msg = ''
1588 raise DownloadError('HTTP %s%s' % (e.code, msg))
1589 except urllib2.URLError, e:
1590 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1591 finally:
1592 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001593
1594 p = None
1595 try:
Conley Owens43bda842012-03-12 11:25:04 -07001596 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001597 unit = 1 << 10
1598
1599 if size and not quiet:
1600 if size > 1024 * 1.3:
1601 unit = 1 << 20
1602 desc = 'MB'
1603 else:
1604 desc = 'KB'
1605 p = Progress(
1606 'Downloading %s' % self.relpath,
1607 int(size) / unit,
1608 units=desc)
1609 if pos > 0:
1610 p.update(pos / unit)
1611
1612 s = 0
1613 while True:
1614 d = r.read(8192)
1615 if d == '':
1616 done = True
1617 return True
1618 dest.write(d)
1619 if p:
1620 s += len(d)
1621 if s >= unit:
1622 p.update(s / unit)
1623 s = s % unit
1624 if p:
1625 if s >= unit:
1626 p.update(s / unit)
1627 else:
1628 p.update(1)
1629 finally:
1630 r.close()
1631 if p:
1632 p.end()
1633 finally:
1634 dest.close()
1635
1636 if os.path.exists(dstPath):
1637 os.remove(dstPath)
1638 if done:
1639 os.rename(tmpPath, dstPath)
1640 elif not keep:
1641 os.remove(tmpPath)
1642
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 def _Checkout(self, rev, quiet=False):
1644 cmd = ['checkout']
1645 if quiet:
1646 cmd.append('-q')
1647 cmd.append(rev)
1648 cmd.append('--')
1649 if GitCommand(self, cmd).Wait() != 0:
1650 if self._allrefs:
1651 raise GitError('%s checkout %s ' % (self.name, rev))
1652
Pierre Tardye5a21222011-03-24 16:28:18 +01001653 def _CherryPick(self, rev, quiet=False):
1654 cmd = ['cherry-pick']
1655 cmd.append(rev)
1656 cmd.append('--')
1657 if GitCommand(self, cmd).Wait() != 0:
1658 if self._allrefs:
1659 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1660
Erwan Mahea94f1622011-08-19 13:56:09 +02001661 def _Revert(self, rev, quiet=False):
1662 cmd = ['revert']
1663 cmd.append('--no-edit')
1664 cmd.append(rev)
1665 cmd.append('--')
1666 if GitCommand(self, cmd).Wait() != 0:
1667 if self._allrefs:
1668 raise GitError('%s revert %s ' % (self.name, rev))
1669
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670 def _ResetHard(self, rev, quiet=True):
1671 cmd = ['reset', '--hard']
1672 if quiet:
1673 cmd.append('-q')
1674 cmd.append(rev)
1675 if GitCommand(self, cmd).Wait() != 0:
1676 raise GitError('%s reset --hard %s ' % (self.name, rev))
1677
1678 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001679 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001680 if onto is not None:
1681 cmd.extend(['--onto', onto])
1682 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001683 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684 raise GitError('%s rebase %s ' % (self.name, upstream))
1685
Pierre Tardy3d125942012-05-04 12:18:12 +02001686 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001688 if ffonly:
1689 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690 if GitCommand(self, cmd).Wait() != 0:
1691 raise GitError('%s merge %s ' % (self.name, head))
1692
1693 def _InitGitDir(self):
1694 if not os.path.exists(self.gitdir):
1695 os.makedirs(self.gitdir)
1696 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001697
Shawn O. Pearce88443382010-10-08 10:02:09 +02001698 mp = self.manifest.manifestProject
1699 ref_dir = mp.config.GetString('repo.reference')
1700
1701 if ref_dir:
1702 mirror_git = os.path.join(ref_dir, self.name + '.git')
1703 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1704 self.relpath + '.git')
1705
1706 if os.path.exists(mirror_git):
1707 ref_dir = mirror_git
1708
1709 elif os.path.exists(repo_git):
1710 ref_dir = repo_git
1711
1712 else:
1713 ref_dir = None
1714
1715 if ref_dir:
1716 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1717 os.path.join(ref_dir, 'objects') + '\n')
1718
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001719 if self.manifest.IsMirror:
1720 self.config.SetString('core.bare', 'true')
1721 else:
1722 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723
1724 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001725 try:
1726 to_rm = os.listdir(hooks)
1727 except OSError:
1728 to_rm = []
1729 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001731 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732
1733 m = self.manifest.manifestProject.config
1734 for key in ['user.name', 'user.email']:
1735 if m.Has(key, include_defaults = False):
1736 self.config.SetString(key, m.GetString(key))
1737
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001738 def _InitHooks(self):
1739 hooks = self._gitdir_path('hooks')
1740 if not os.path.exists(hooks):
1741 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001742 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001743 name = os.path.basename(stock_hook)
1744
Victor Boivie65e0f352011-04-18 11:23:29 +02001745 if name in ('commit-msg',) and not self.remote.review \
1746 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001747 # Don't install a Gerrit Code Review hook if this
1748 # project does not appear to use it for reviews.
1749 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001750 # Since the manifest project is one of those, but also
1751 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001752 continue
1753
1754 dst = os.path.join(hooks, name)
1755 if os.path.islink(dst):
1756 continue
1757 if os.path.exists(dst):
1758 if filecmp.cmp(stock_hook, dst, shallow=False):
1759 os.remove(dst)
1760 else:
1761 _error("%s: Not replacing %s hook", self.relpath, name)
1762 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001763 try:
1764 os.symlink(relpath(stock_hook, dst), dst)
1765 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001766 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001767 raise GitError('filesystem must support symlinks')
1768 else:
1769 raise
1770
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001771 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001772 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001774 remote.url = self.remote.url
1775 remote.review = self.remote.review
1776 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001778 if self.worktree:
1779 remote.ResetFetch(mirror=False)
1780 else:
1781 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782 remote.Save()
1783
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 def _InitMRef(self):
1785 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001786 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001788 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001789 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001790
1791 def _InitAnyMRef(self, ref):
1792 cur = self.bare_ref.symref(ref)
1793
1794 if self.revisionId:
1795 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1796 msg = 'manifest set to %s' % self.revisionId
1797 dst = self.revisionId + '^0'
1798 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1799 else:
1800 remote = self.GetRemote(self.remote.name)
1801 dst = remote.ToLocal(self.revisionExpr)
1802 if cur != dst:
1803 msg = 'manifest set to %s' % self.revisionExpr
1804 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001805
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001806 def _InitWorkTree(self):
1807 dotgit = os.path.join(self.worktree, '.git')
1808 if not os.path.exists(dotgit):
1809 os.makedirs(dotgit)
1810
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811 for name in ['config',
1812 'description',
1813 'hooks',
1814 'info',
1815 'logs',
1816 'objects',
1817 'packed-refs',
1818 'refs',
1819 'rr-cache',
1820 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001821 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001822 src = os.path.join(self.gitdir, name)
1823 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001824 if os.path.islink(dst) or not os.path.exists(dst):
1825 os.symlink(relpath(src, dst), dst)
1826 else:
1827 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001828 except OSError, e:
1829 if e.errno == errno.EPERM:
1830 raise GitError('filesystem must support symlinks')
1831 else:
1832 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001834 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835
1836 cmd = ['read-tree', '--reset', '-u']
1837 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001838 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001839 if GitCommand(self, cmd).Wait() != 0:
1840 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001841
1842 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1843 if not os.path.exists(rr_cache):
1844 os.makedirs(rr_cache)
1845
Shawn O. Pearce93609662009-04-21 10:50:33 -07001846 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001847
1848 def _gitdir_path(self, path):
1849 return os.path.join(self.gitdir, path)
1850
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001851 def _revlist(self, *args, **kw):
1852 a = []
1853 a.extend(args)
1854 a.append('--')
1855 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001856
1857 @property
1858 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001859 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001860
1861 class _GitGetByExec(object):
1862 def __init__(self, project, bare):
1863 self._project = project
1864 self._bare = bare
1865
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001866 def LsOthers(self):
1867 p = GitCommand(self._project,
1868 ['ls-files',
1869 '-z',
1870 '--others',
1871 '--exclude-standard'],
1872 bare = False,
1873 capture_stdout = True,
1874 capture_stderr = True)
1875 if p.Wait() == 0:
1876 out = p.stdout
1877 if out:
1878 return out[:-1].split("\0")
1879 return []
1880
1881 def DiffZ(self, name, *args):
1882 cmd = [name]
1883 cmd.append('-z')
1884 cmd.extend(args)
1885 p = GitCommand(self._project,
1886 cmd,
1887 bare = False,
1888 capture_stdout = True,
1889 capture_stderr = True)
1890 try:
1891 out = p.process.stdout.read()
1892 r = {}
1893 if out:
1894 out = iter(out[:-1].split('\0'))
1895 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001896 try:
1897 info = out.next()
1898 path = out.next()
1899 except StopIteration:
1900 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001901
1902 class _Info(object):
1903 def __init__(self, path, omode, nmode, oid, nid, state):
1904 self.path = path
1905 self.src_path = None
1906 self.old_mode = omode
1907 self.new_mode = nmode
1908 self.old_id = oid
1909 self.new_id = nid
1910
1911 if len(state) == 1:
1912 self.status = state
1913 self.level = None
1914 else:
1915 self.status = state[:1]
1916 self.level = state[1:]
1917 while self.level.startswith('0'):
1918 self.level = self.level[1:]
1919
1920 info = info[1:].split(' ')
1921 info =_Info(path, *info)
1922 if info.status in ('R', 'C'):
1923 info.src_path = info.path
1924 info.path = out.next()
1925 r[info.path] = info
1926 return r
1927 finally:
1928 p.Wait()
1929
1930 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001931 if self._bare:
1932 path = os.path.join(self._project.gitdir, HEAD)
1933 else:
1934 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001935 fd = open(path, 'rb')
1936 try:
1937 line = fd.read()
1938 finally:
1939 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001940 if line.startswith('ref: '):
1941 return line[5:-1]
1942 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001943
1944 def SetHead(self, ref, message=None):
1945 cmdv = []
1946 if message is not None:
1947 cmdv.extend(['-m', message])
1948 cmdv.append(HEAD)
1949 cmdv.append(ref)
1950 self.symbolic_ref(*cmdv)
1951
1952 def DetachHead(self, new, message=None):
1953 cmdv = ['--no-deref']
1954 if message is not None:
1955 cmdv.extend(['-m', message])
1956 cmdv.append(HEAD)
1957 cmdv.append(new)
1958 self.update_ref(*cmdv)
1959
1960 def UpdateRef(self, name, new, old=None,
1961 message=None,
1962 detach=False):
1963 cmdv = []
1964 if message is not None:
1965 cmdv.extend(['-m', message])
1966 if detach:
1967 cmdv.append('--no-deref')
1968 cmdv.append(name)
1969 cmdv.append(new)
1970 if old is not None:
1971 cmdv.append(old)
1972 self.update_ref(*cmdv)
1973
1974 def DeleteRef(self, name, old=None):
1975 if not old:
1976 old = self.rev_parse(name)
1977 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001978 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001979
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001980 def rev_list(self, *args, **kw):
1981 if 'format' in kw:
1982 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1983 else:
1984 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001985 cmdv.extend(args)
1986 p = GitCommand(self._project,
1987 cmdv,
1988 bare = self._bare,
1989 capture_stdout = True,
1990 capture_stderr = True)
1991 r = []
1992 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001993 if line[-1] == '\n':
1994 line = line[:-1]
1995 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996 if p.Wait() != 0:
1997 raise GitError('%s rev-list %s: %s' % (
1998 self._project.name,
1999 str(args),
2000 p.stderr))
2001 return r
2002
2003 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002004 """Allow arbitrary git commands using pythonic syntax.
2005
2006 This allows you to do things like:
2007 git_obj.rev_parse('HEAD')
2008
2009 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2010 run. We'll replace the '_' with a '-' and try to run a git command.
2011 Any other arguments will be passed to the git command.
2012
2013 Args:
2014 name: The name of the git command to call. Any '_' characters will
2015 be replaced with '-'.
2016
2017 Returns:
2018 A callable object that will try to call git with the named command.
2019 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002020 name = name.replace('_', '-')
2021 def runner(*args):
2022 cmdv = [name]
2023 cmdv.extend(args)
2024 p = GitCommand(self._project,
2025 cmdv,
2026 bare = self._bare,
2027 capture_stdout = True,
2028 capture_stderr = True)
2029 if p.Wait() != 0:
2030 raise GitError('%s %s: %s' % (
2031 self._project.name,
2032 name,
2033 p.stderr))
2034 r = p.stdout
2035 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2036 return r[:-1]
2037 return r
2038 return runner
2039
2040
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002041class _PriorSyncFailedError(Exception):
2042 def __str__(self):
2043 return 'prior sync failed; rebase still in progress'
2044
2045class _DirtyError(Exception):
2046 def __str__(self):
2047 return 'contains uncommitted changes'
2048
2049class _InfoMessage(object):
2050 def __init__(self, project, text):
2051 self.project = project
2052 self.text = text
2053
2054 def Print(self, syncbuf):
2055 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2056 syncbuf.out.nl()
2057
2058class _Failure(object):
2059 def __init__(self, project, why):
2060 self.project = project
2061 self.why = why
2062
2063 def Print(self, syncbuf):
2064 syncbuf.out.fail('error: %s/: %s',
2065 self.project.relpath,
2066 str(self.why))
2067 syncbuf.out.nl()
2068
2069class _Later(object):
2070 def __init__(self, project, action):
2071 self.project = project
2072 self.action = action
2073
2074 def Run(self, syncbuf):
2075 out = syncbuf.out
2076 out.project('project %s/', self.project.relpath)
2077 out.nl()
2078 try:
2079 self.action()
2080 out.nl()
2081 return True
2082 except GitError, e:
2083 out.nl()
2084 return False
2085
2086class _SyncColoring(Coloring):
2087 def __init__(self, config):
2088 Coloring.__init__(self, config, 'reposync')
2089 self.project = self.printer('header', attr = 'bold')
2090 self.info = self.printer('info')
2091 self.fail = self.printer('fail', fg='red')
2092
2093class SyncBuffer(object):
2094 def __init__(self, config, detach_head=False):
2095 self._messages = []
2096 self._failures = []
2097 self._later_queue1 = []
2098 self._later_queue2 = []
2099
2100 self.out = _SyncColoring(config)
2101 self.out.redirect(sys.stderr)
2102
2103 self.detach_head = detach_head
2104 self.clean = True
2105
2106 def info(self, project, fmt, *args):
2107 self._messages.append(_InfoMessage(project, fmt % args))
2108
2109 def fail(self, project, err=None):
2110 self._failures.append(_Failure(project, err))
2111 self.clean = False
2112
2113 def later1(self, project, what):
2114 self._later_queue1.append(_Later(project, what))
2115
2116 def later2(self, project, what):
2117 self._later_queue2.append(_Later(project, what))
2118
2119 def Finish(self):
2120 self._PrintMessages()
2121 self._RunLater()
2122 self._PrintMessages()
2123 return self.clean
2124
2125 def _RunLater(self):
2126 for q in ['_later_queue1', '_later_queue2']:
2127 if not self._RunQueue(q):
2128 return
2129
2130 def _RunQueue(self, queue):
2131 for m in getattr(self, queue):
2132 if not m.Run(self):
2133 self.clean = False
2134 return False
2135 setattr(self, queue, [])
2136 return True
2137
2138 def _PrintMessages(self):
2139 for m in self._messages:
2140 m.Print(self)
2141 for m in self._failures:
2142 m.Print(self)
2143
2144 self._messages = []
2145 self._failures = []
2146
2147
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148class MetaProject(Project):
2149 """A special project housed under .repo.
2150 """
2151 def __init__(self, manifest, name, gitdir, worktree):
2152 repodir = manifest.repodir
2153 Project.__init__(self,
2154 manifest = manifest,
2155 name = name,
2156 gitdir = gitdir,
2157 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002158 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002159 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002160 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002161 revisionId = None,
2162 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002163
2164 def PreSync(self):
2165 if self.Exists:
2166 cb = self.CurrentBranch
2167 if cb:
2168 base = self.GetBranch(cb).merge
2169 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002170 self.revisionExpr = base
2171 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002172
Florian Vallee5d016502012-06-07 17:19:26 +02002173 def MetaBranchSwitch(self, target):
2174 """ Prepare MetaProject for manifest branch switch
2175 """
2176
2177 # detach and delete manifest branch, allowing a new
2178 # branch to take over
2179 syncbuf = SyncBuffer(self.config, detach_head = True)
2180 self.Sync_LocalHalf(syncbuf)
2181 syncbuf.Finish()
2182
2183 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002184 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002185 capture_stdout = True,
2186 capture_stderr = True).Wait() == 0
2187
2188
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002189 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002190 def LastFetch(self):
2191 try:
2192 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2193 return os.path.getmtime(fh)
2194 except OSError:
2195 return 0
2196
2197 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002198 def HasChanges(self):
2199 """Has the remote received new commits not yet checked out?
2200 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002201 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002202 return False
2203
2204 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002205 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002206 head = self.work_git.GetHead()
2207 if head.startswith(R_HEADS):
2208 try:
2209 head = all[head]
2210 except KeyError:
2211 head = None
2212
2213 if revid == head:
2214 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002215 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002216 return True
2217 return False