blob: 4cef856bf44519c8788ea5c70594dab9ed958cf2 [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
19import re
20import shutil
21import stat
22import sys
23import urllib2
24
25from color import Coloring
26from git_command import GitCommand
27from git_config import GitConfig, IsId
Doug Anderson37282b42011-03-04 11:54:18 -080028from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080029from error import ManifestInvalidRevisionError
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030
Shawn O. Pearced237b692009-04-17 18:49:50 -070031from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070033def _lwrite(path, content):
34 lock = '%s.lock' % path
35
36 fd = open(lock, 'wb')
37 try:
38 fd.write(content)
39 finally:
40 fd.close()
41
42 try:
43 os.rename(lock, path)
44 except OSError:
45 os.remove(lock)
46 raise
47
Shawn O. Pearce48244782009-04-16 08:25:57 -070048def _error(fmt, *args):
49 msg = fmt % args
50 print >>sys.stderr, 'error: %s' % msg
51
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052def not_rev(r):
53 return '^' + r
54
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080055def sq(r):
56 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080057
Doug Anderson8ced8642011-01-10 14:16:30 -080058_project_hook_list = None
59def _ProjectHooks():
60 """List the hooks present in the 'hooks' directory.
61
62 These hooks are project hooks and are copied to the '.git/hooks' directory
63 of all subprojects.
64
65 This function caches the list of hooks (based on the contents of the
66 'repo/hooks' directory) on the first call.
67
68 Returns:
69 A list of absolute paths to all of the files in the hooks directory.
70 """
71 global _project_hook_list
72 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080073 d = os.path.abspath(os.path.dirname(__file__))
74 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080075 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
76 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080077
78def relpath(dst, src):
79 src = os.path.dirname(src)
80 top = os.path.commonprefix([dst, src])
81 if top.endswith('/'):
82 top = top[:-1]
83 else:
84 top = os.path.dirname(top)
85
86 tmp = src
87 rel = ''
88 while top != tmp:
89 rel += '../'
90 tmp = os.path.dirname(tmp)
91 return rel + dst[len(top) + 1:]
92
93
Shawn O. Pearce632768b2008-10-23 11:58:52 -070094class DownloadedChange(object):
95 _commit_cache = None
96
97 def __init__(self, project, base, change_id, ps_id, commit):
98 self.project = project
99 self.base = base
100 self.change_id = change_id
101 self.ps_id = ps_id
102 self.commit = commit
103
104 @property
105 def commits(self):
106 if self._commit_cache is None:
107 self._commit_cache = self.project.bare_git.rev_list(
108 '--abbrev=8',
109 '--abbrev-commit',
110 '--pretty=oneline',
111 '--reverse',
112 '--date-order',
113 not_rev(self.base),
114 self.commit,
115 '--')
116 return self._commit_cache
117
118
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119class ReviewableBranch(object):
120 _commit_cache = None
121
122 def __init__(self, project, branch, base):
123 self.project = project
124 self.branch = branch
125 self.base = base
126
127 @property
128 def name(self):
129 return self.branch.name
130
131 @property
132 def commits(self):
133 if self._commit_cache is None:
134 self._commit_cache = self.project.bare_git.rev_list(
135 '--abbrev=8',
136 '--abbrev-commit',
137 '--pretty=oneline',
138 '--reverse',
139 '--date-order',
140 not_rev(self.base),
141 R_HEADS + self.name,
142 '--')
143 return self._commit_cache
144
145 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800146 def unabbrev_commits(self):
147 r = dict()
148 for commit in self.project.bare_git.rev_list(
149 not_rev(self.base),
150 R_HEADS + self.name,
151 '--'):
152 r[commit[0:8]] = commit
153 return r
154
155 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700156 def date(self):
157 return self.project.bare_git.log(
158 '--pretty=format:%cd',
159 '-n', '1',
160 R_HEADS + self.name,
161 '--')
162
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700163 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800164 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700165 people,
166 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700168 def GetPublishedRefs(self):
169 refs = {}
170 output = self.project.bare_git.ls_remote(
171 self.branch.remote.SshReviewUrl(self.project.UserEmail),
172 'refs/changes/*')
173 for line in output.split('\n'):
174 try:
175 (sha, ref) = line.split()
176 refs[sha] = ref
177 except ValueError:
178 pass
179
180 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182class StatusColoring(Coloring):
183 def __init__(self, config):
184 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold')
186 self.branch = self.printer('header', attr = 'bold')
187 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700188 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 self.added = self.printer('added', fg = 'green')
191 self.changed = self.printer('changed', fg = 'red')
192 self.untracked = self.printer('untracked', fg = 'red')
193
194
195class DiffColoring(Coloring):
196 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold')
199
200
201class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800202 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 self.src = src
204 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800205 self.abs_src = abssrc
206 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207
208 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800209 src = self.abs_src
210 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 # copy file if it does not exist or is out of date
212 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
213 try:
214 # remove existing file first, since it might be read-only
215 if os.path.exists(dest):
216 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400217 else:
218 dir = os.path.dirname(dest)
219 if not os.path.isdir(dir):
220 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 shutil.copy(src, dest)
222 # make the file read-only
223 mode = os.stat(dest)[stat.ST_MODE]
224 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
225 os.chmod(dest, mode)
226 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700227 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700229class RemoteSpec(object):
230 def __init__(self,
231 name,
232 url = None,
233 review = None):
234 self.name = name
235 self.url = url
236 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Doug Anderson37282b42011-03-04 11:54:18 -0800238class RepoHook(object):
239 """A RepoHook contains information about a script to run as a hook.
240
241 Hooks are used to run a python script before running an upload (for instance,
242 to run presubmit checks). Eventually, we may have hooks for other actions.
243
244 This shouldn't be confused with files in the 'repo/hooks' directory. Those
245 files are copied into each '.git/hooks' folder for each project. Repo-level
246 hooks are associated instead with repo actions.
247
248 Hooks are always python. When a hook is run, we will load the hook into the
249 interpreter and execute its main() function.
250 """
251 def __init__(self,
252 hook_type,
253 hooks_project,
254 topdir,
255 abort_if_user_denies=False):
256 """RepoHook constructor.
257
258 Params:
259 hook_type: A string representing the type of hook. This is also used
260 to figure out the name of the file containing the hook. For
261 example: 'pre-upload'.
262 hooks_project: The project containing the repo hooks. If you have a
263 manifest, this is manifest.repo_hooks_project. OK if this is None,
264 which will make the hook a no-op.
265 topdir: Repo's top directory (the one containing the .repo directory).
266 Scripts will run with CWD as this directory. If you have a manifest,
267 this is manifest.topdir
268 abort_if_user_denies: If True, we'll throw a HookError() if the user
269 doesn't allow us to run the hook.
270 """
271 self._hook_type = hook_type
272 self._hooks_project = hooks_project
273 self._topdir = topdir
274 self._abort_if_user_denies = abort_if_user_denies
275
276 # Store the full path to the script for convenience.
277 if self._hooks_project:
278 self._script_fullpath = os.path.join(self._hooks_project.worktree,
279 self._hook_type + '.py')
280 else:
281 self._script_fullpath = None
282
283 def _GetHash(self):
284 """Return a hash of the contents of the hooks directory.
285
286 We'll just use git to do this. This hash has the property that if anything
287 changes in the directory we will return a different has.
288
289 SECURITY CONSIDERATION:
290 This hash only represents the contents of files in the hook directory, not
291 any other files imported or called by hooks. Changes to imported files
292 can change the script behavior without affecting the hash.
293
294 Returns:
295 A string representing the hash. This will always be ASCII so that it can
296 be printed to the user easily.
297 """
298 assert self._hooks_project, "Must have hooks to calculate their hash."
299
300 # We will use the work_git object rather than just calling GetRevisionId().
301 # That gives us a hash of the latest checked in version of the files that
302 # the user will actually be executing. Specifically, GetRevisionId()
303 # doesn't appear to change even if a user checks out a different version
304 # of the hooks repo (via git checkout) nor if a user commits their own revs.
305 #
306 # NOTE: Local (non-committed) changes will not be factored into this hash.
307 # I think this is OK, since we're really only worried about warning the user
308 # about upstream changes.
309 return self._hooks_project.work_git.rev_parse('HEAD')
310
311 def _GetMustVerb(self):
312 """Return 'must' if the hook is required; 'should' if not."""
313 if self._abort_if_user_denies:
314 return 'must'
315 else:
316 return 'should'
317
318 def _CheckForHookApproval(self):
319 """Check to see whether this hook has been approved.
320
321 We'll look at the hash of all of the hooks. If this matches the hash that
322 the user last approved, we're done. If it doesn't, we'll ask the user
323 about approval.
324
325 Note that we ask permission for each individual hook even though we use
326 the hash of all hooks when detecting changes. We'd like the user to be
327 able to approve / deny each hook individually. We only use the hash of all
328 hooks because there is no other easy way to detect changes to local imports.
329
330 Returns:
331 True if this hook is approved to run; False otherwise.
332
333 Raises:
334 HookError: Raised if the user doesn't approve and abort_if_user_denies
335 was passed to the consturctor.
336 """
337 hooks_dir = self._hooks_project.worktree
338 hooks_config = self._hooks_project.config
339 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
340
341 # Get the last hash that the user approved for this hook; may be None.
342 old_hash = hooks_config.GetString(git_approval_key)
343
344 # Get the current hash so we can tell if scripts changed since approval.
345 new_hash = self._GetHash()
346
347 if old_hash is not None:
348 # User previously approved hook and asked not to be prompted again.
349 if new_hash == old_hash:
350 # Approval matched. We're done.
351 return True
352 else:
353 # Give the user a reason why we're prompting, since they last told
354 # us to "never ask again".
355 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
356 self._hook_type)
357 else:
358 prompt = ''
359
360 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
361 if sys.stdout.isatty():
362 prompt += ('Repo %s run the script:\n'
363 ' %s\n'
364 '\n'
365 'Do you want to allow this script to run '
366 '(yes/yes-never-ask-again/NO)? ') % (
367 self._GetMustVerb(), self._script_fullpath)
368 response = raw_input(prompt).lower()
369 print
370
371 # User is doing a one-time approval.
372 if response in ('y', 'yes'):
373 return True
374 elif response == 'yes-never-ask-again':
375 hooks_config.SetString(git_approval_key, new_hash)
376 return True
377
378 # For anything else, we'll assume no approval.
379 if self._abort_if_user_denies:
380 raise HookError('You must allow the %s hook or use --no-verify.' %
381 self._hook_type)
382
383 return False
384
385 def _ExecuteHook(self, **kwargs):
386 """Actually execute the given hook.
387
388 This will run the hook's 'main' function in our python interpreter.
389
390 Args:
391 kwargs: Keyword arguments to pass to the hook. These are often specific
392 to the hook type. For instance, pre-upload hooks will contain
393 a project_list.
394 """
395 # Keep sys.path and CWD stashed away so that we can always restore them
396 # upon function exit.
397 orig_path = os.getcwd()
398 orig_syspath = sys.path
399
400 try:
401 # Always run hooks with CWD as topdir.
402 os.chdir(self._topdir)
403
404 # Put the hook dir as the first item of sys.path so hooks can do
405 # relative imports. We want to replace the repo dir as [0] so
406 # hooks can't import repo files.
407 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
408
409 # Exec, storing global context in the context dict. We catch exceptions
410 # and convert to a HookError w/ just the failing traceback.
411 context = {}
412 try:
413 execfile(self._script_fullpath, context)
414 except Exception:
415 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
416 traceback.format_exc(), self._hook_type))
417
418 # Running the script should have defined a main() function.
419 if 'main' not in context:
420 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
421
422
423 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
424 # We don't actually want hooks to define their main with this argument--
425 # it's there to remind them that their hook should always take **kwargs.
426 # For instance, a pre-upload hook should be defined like:
427 # def main(project_list, **kwargs):
428 #
429 # This allows us to later expand the API without breaking old hooks.
430 kwargs = kwargs.copy()
431 kwargs['hook_should_take_kwargs'] = True
432
433 # Call the main function in the hook. If the hook should cause the
434 # build to fail, it will raise an Exception. We'll catch that convert
435 # to a HookError w/ just the failing traceback.
436 try:
437 context['main'](**kwargs)
438 except Exception:
439 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
440 'above.' % (
441 traceback.format_exc(), self._hook_type))
442 finally:
443 # Restore sys.path and CWD.
444 sys.path = orig_syspath
445 os.chdir(orig_path)
446
447 def Run(self, user_allows_all_hooks, **kwargs):
448 """Run the hook.
449
450 If the hook doesn't exist (because there is no hooks project or because
451 this particular hook is not enabled), this is a no-op.
452
453 Args:
454 user_allows_all_hooks: If True, we will never prompt about running the
455 hook--we'll just assume it's OK to run it.
456 kwargs: Keyword arguments to pass to the hook. These are often specific
457 to the hook type. For instance, pre-upload hooks will contain
458 a project_list.
459
460 Raises:
461 HookError: If there was a problem finding the hook or the user declined
462 to run a required hook (from _CheckForHookApproval).
463 """
464 # No-op if there is no hooks project or if hook is disabled.
465 if ((not self._hooks_project) or
466 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
467 return
468
469 # Bail with a nice error if we can't find the hook.
470 if not os.path.isfile(self._script_fullpath):
471 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
472
473 # Make sure the user is OK with running the hook.
474 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
475 return
476
477 # Run the hook with the same version of python we're using.
478 self._ExecuteHook(**kwargs)
479
480
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700481class Project(object):
482 def __init__(self,
483 manifest,
484 name,
485 remote,
486 gitdir,
487 worktree,
488 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700489 revisionExpr,
490 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700491 self.manifest = manifest
492 self.name = name
493 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800494 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800495 if worktree:
496 self.worktree = worktree.replace('\\', '/')
497 else:
498 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700499 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700500 self.revisionExpr = revisionExpr
501
502 if revisionId is None \
503 and revisionExpr \
504 and IsId(revisionExpr):
505 self.revisionId = revisionExpr
506 else:
507 self.revisionId = revisionId
508
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700510 self.copyfiles = []
511 self.config = GitConfig.ForRepository(
512 gitdir = self.gitdir,
513 defaults = self.manifest.globalConfig)
514
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800515 if self.worktree:
516 self.work_git = self._GitGetByExec(self, bare=False)
517 else:
518 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700519 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700520 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700521
Doug Anderson37282b42011-03-04 11:54:18 -0800522 # This will be filled in if a project is later identified to be the
523 # project containing repo hooks.
524 self.enabled_repo_hooks = []
525
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 @property
527 def Exists(self):
528 return os.path.isdir(self.gitdir)
529
530 @property
531 def CurrentBranch(self):
532 """Obtain the name of the currently checked out branch.
533 The branch name omits the 'refs/heads/' prefix.
534 None is returned if the project is on a detached HEAD.
535 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700536 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 if b.startswith(R_HEADS):
538 return b[len(R_HEADS):]
539 return None
540
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700541 def IsRebaseInProgress(self):
542 w = self.worktree
543 g = os.path.join(w, '.git')
544 return os.path.exists(os.path.join(g, 'rebase-apply')) \
545 or os.path.exists(os.path.join(g, 'rebase-merge')) \
546 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 def IsDirty(self, consider_untracked=True):
549 """Is the working directory modified in some way?
550 """
551 self.work_git.update_index('-q',
552 '--unmerged',
553 '--ignore-missing',
554 '--refresh')
555 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
556 return True
557 if self.work_git.DiffZ('diff-files'):
558 return True
559 if consider_untracked and self.work_git.LsOthers():
560 return True
561 return False
562
563 _userident_name = None
564 _userident_email = None
565
566 @property
567 def UserName(self):
568 """Obtain the user's personal name.
569 """
570 if self._userident_name is None:
571 self._LoadUserIdentity()
572 return self._userident_name
573
574 @property
575 def UserEmail(self):
576 """Obtain the user's email address. This is very likely
577 to be their Gerrit login.
578 """
579 if self._userident_email is None:
580 self._LoadUserIdentity()
581 return self._userident_email
582
583 def _LoadUserIdentity(self):
584 u = self.bare_git.var('GIT_COMMITTER_IDENT')
585 m = re.compile("^(.*) <([^>]*)> ").match(u)
586 if m:
587 self._userident_name = m.group(1)
588 self._userident_email = m.group(2)
589 else:
590 self._userident_name = ''
591 self._userident_email = ''
592
593 def GetRemote(self, name):
594 """Get the configuration for a single remote.
595 """
596 return self.config.GetRemote(name)
597
598 def GetBranch(self, name):
599 """Get the configuration for a single branch.
600 """
601 return self.config.GetBranch(name)
602
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700603 def GetBranches(self):
604 """Get all existing local branches.
605 """
606 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700607 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700608 heads = {}
609 pubd = {}
610
611 for name, id in all.iteritems():
612 if name.startswith(R_HEADS):
613 name = name[len(R_HEADS):]
614 b = self.GetBranch(name)
615 b.current = name == current
616 b.published = None
617 b.revision = id
618 heads[name] = b
619
620 for name, id in all.iteritems():
621 if name.startswith(R_PUB):
622 name = name[len(R_PUB):]
623 b = heads.get(name)
624 if b:
625 b.published = id
626
627 return heads
628
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629
630## Status Display ##
631
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500632 def HasChanges(self):
633 """Returns true if there are uncommitted changes.
634 """
635 self.work_git.update_index('-q',
636 '--unmerged',
637 '--ignore-missing',
638 '--refresh')
639 if self.IsRebaseInProgress():
640 return True
641
642 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
643 return True
644
645 if self.work_git.DiffZ('diff-files'):
646 return True
647
648 if self.work_git.LsOthers():
649 return True
650
651 return False
652
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 def PrintWorkTreeStatus(self):
654 """Prints the status of the repository to stdout.
655 """
656 if not os.path.isdir(self.worktree):
657 print ''
658 print 'project %s/' % self.relpath
659 print ' missing (run "repo sync")'
660 return
661
662 self.work_git.update_index('-q',
663 '--unmerged',
664 '--ignore-missing',
665 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700666 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
668 df = self.work_git.DiffZ('diff-files')
669 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700670 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700671 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672
673 out = StatusColoring(self.config)
674 out.project('project %-40s', self.relpath + '/')
675
676 branch = self.CurrentBranch
677 if branch is None:
678 out.nobranch('(*** NO BRANCH ***)')
679 else:
680 out.branch('branch %s', branch)
681 out.nl()
682
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700683 if rb:
684 out.important('prior sync failed; rebase still in progress')
685 out.nl()
686
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700687 paths = list()
688 paths.extend(di.keys())
689 paths.extend(df.keys())
690 paths.extend(do)
691
692 paths = list(set(paths))
693 paths.sort()
694
695 for p in paths:
696 try: i = di[p]
697 except KeyError: i = None
698
699 try: f = df[p]
700 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200701
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 if i: i_status = i.status.upper()
703 else: i_status = '-'
704
705 if f: f_status = f.status.lower()
706 else: f_status = '-'
707
708 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800709 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710 i.src_path, p, i.level)
711 else:
712 line = ' %s%s\t%s' % (i_status, f_status, p)
713
714 if i and not f:
715 out.added('%s', line)
716 elif (i and f) or (not i and f):
717 out.changed('%s', line)
718 elif not i and not f:
719 out.untracked('%s', line)
720 else:
721 out.write('%s', line)
722 out.nl()
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700723 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724
725 def PrintWorkTreeDiff(self):
726 """Prints the status of the repository to stdout.
727 """
728 out = DiffColoring(self.config)
729 cmd = ['diff']
730 if out.is_on:
731 cmd.append('--color')
732 cmd.append(HEAD)
733 cmd.append('--')
734 p = GitCommand(self,
735 cmd,
736 capture_stdout = True,
737 capture_stderr = True)
738 has_diff = False
739 for line in p.process.stdout:
740 if not has_diff:
741 out.nl()
742 out.project('project %s/' % self.relpath)
743 out.nl()
744 has_diff = True
745 print line[:-1]
746 p.Wait()
747
748
749## Publish / Upload ##
750
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700751 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 """Was the branch published (uploaded) for code review?
753 If so, returns the SHA-1 hash of the last published
754 state for the branch.
755 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700756 key = R_PUB + branch
757 if all is None:
758 try:
759 return self.bare_git.rev_parse(key)
760 except GitError:
761 return None
762 else:
763 try:
764 return all[key]
765 except KeyError:
766 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700768 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 """Prunes any stale published refs.
770 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700771 if all is None:
772 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700773 heads = set()
774 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700775 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 if name.startswith(R_HEADS):
777 heads.add(name)
778 elif name.startswith(R_PUB):
779 canrm[name] = id
780
781 for name, id in canrm.iteritems():
782 n = name[len(R_PUB):]
783 if R_HEADS + n not in heads:
784 self.bare_git.DeleteRef(name, id)
785
786 def GetUploadableBranches(self):
787 """List any branches which can be uploaded for review.
788 """
789 heads = {}
790 pubed = {}
791
792 for name, id in self._allrefs.iteritems():
793 if name.startswith(R_HEADS):
794 heads[name[len(R_HEADS):]] = id
795 elif name.startswith(R_PUB):
796 pubed[name[len(R_PUB):]] = id
797
798 ready = []
799 for branch, id in heads.iteritems():
800 if branch in pubed and pubed[branch] == id:
801 continue
802
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800803 rb = self.GetUploadableBranch(branch)
804 if rb:
805 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806 return ready
807
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800808 def GetUploadableBranch(self, branch_name):
809 """Get a single uploadable branch, or None.
810 """
811 branch = self.GetBranch(branch_name)
812 base = branch.LocalMerge
813 if branch.LocalMerge:
814 rb = ReviewableBranch(self, branch, base)
815 if rb.commits:
816 return rb
817 return None
818
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700819 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700820 people=([],[]),
821 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822 """Uploads the named branch for code review.
823 """
824 if branch is None:
825 branch = self.CurrentBranch
826 if branch is None:
827 raise GitError('not currently on a branch')
828
829 branch = self.GetBranch(branch)
830 if not branch.LocalMerge:
831 raise GitError('branch %s does not track a remote' % branch.name)
832 if not branch.remote.review:
833 raise GitError('remote %s has no review url' % branch.remote.name)
834
835 dest_branch = branch.merge
836 if not dest_branch.startswith(R_HEADS):
837 dest_branch = R_HEADS + dest_branch
838
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800839 if not branch.remote.projectname:
840 branch.remote.projectname = self.name
841 branch.remote.Save()
842
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800843 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800844 if dest_branch.startswith(R_HEADS):
845 dest_branch = dest_branch[len(R_HEADS):]
846
847 rp = ['gerrit receive-pack']
848 for e in people[0]:
849 rp.append('--reviewer=%s' % sq(e))
850 for e in people[1]:
851 rp.append('--cc=%s' % sq(e))
852
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700853 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
854 if auto_topic:
855 ref_spec = ref_spec + '/' + branch.name
856
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800857 cmd = ['push']
858 cmd.append('--receive-pack=%s' % " ".join(rp))
859 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700860 cmd.append(ref_spec)
861
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800862 if GitCommand(self, cmd, bare = True).Wait() != 0:
863 raise UploadError('Upload failed')
864
865 else:
866 raise UploadError('Unsupported protocol %s' \
867 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
869 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
870 self.bare_git.UpdateRef(R_PUB + branch.name,
871 R_HEADS + branch.name,
872 message = msg)
873
874
875## Sync ##
876
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700877 def Sync_NetworkHalf(self, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 """Perform only the network IO portion of the sync process.
879 Local working directory/branch state is not affected.
880 """
Shawn O. Pearce88443382010-10-08 10:02:09 +0200881 is_new = not self.Exists
882 if is_new:
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700883 if not quiet:
884 print >>sys.stderr
885 print >>sys.stderr, 'Initializing project %s ...' % self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800887
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 self._InitRemote()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700889 if not self._RemoteFetch(initial=is_new, quiet=quiet):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800891
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200892 #Check that the requested ref was found after fetch
893 #
894 try:
895 self.GetRevisionId()
896 except ManifestInvalidRevisionError:
897 # if the ref is a tag. We can try fetching
898 # the tag manually as a last resort
899 #
900 rev = self.revisionExpr
901 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700902 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200903
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800904 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800905 self._InitMRef()
906 else:
907 self._InitMirrorHead()
908 try:
909 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
910 except OSError:
911 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800913
914 def PostRepoUpgrade(self):
915 self._InitHooks()
916
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 def _CopyFiles(self):
918 for file in self.copyfiles:
919 file._Copy()
920
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700921 def GetRevisionId(self, all=None):
922 if self.revisionId:
923 return self.revisionId
924
925 rem = self.GetRemote(self.remote.name)
926 rev = rem.ToLocal(self.revisionExpr)
927
928 if all is not None and rev in all:
929 return all[rev]
930
931 try:
932 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
933 except GitError:
934 raise ManifestInvalidRevisionError(
935 'revision %s in %s not found' % (self.revisionExpr,
936 self.name))
937
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700938 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 """Perform only the local IO portion of the sync process.
940 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700942 all = self.bare_ref.all
943 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700944 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800945
946 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700947 head = self.work_git.GetHead()
948 if head.startswith(R_HEADS):
949 branch = head[len(R_HEADS):]
950 try:
951 head = all[head]
952 except KeyError:
953 head = None
954 else:
955 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700957 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 # Currently on a detached HEAD. The user is assumed to
959 # not have any local modifications worth worrying about.
960 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700961 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700962 syncbuf.fail(self, _PriorSyncFailedError())
963 return
964
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700965 if head == revid:
966 # No changes; don't do anything further.
967 #
968 return
969
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700970 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700972 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700974 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700975 except GitError, e:
976 syncbuf.fail(self, e)
977 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700979 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700981 if head == revid:
982 # No changes; don't do anything further.
983 #
984 return
985
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700988 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 # The current branch has no tracking configuration.
990 # Jump off it to a deatched HEAD.
991 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700992 syncbuf.info(self,
993 "leaving %s; does not track upstream",
994 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700996 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700997 except GitError, e:
998 syncbuf.fail(self, e)
999 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001001 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001003 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001004 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001006 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 if not_merged:
1008 if upstream_gain:
1009 # The user has published this branch and some of those
1010 # commits are not yet merged upstream. We do not want
1011 # to rewrite the published commits so we punt.
1012 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001013 syncbuf.fail(self,
1014 "branch %s is published (but not merged) and is now %d commits behind"
1015 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001016 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001017 elif pub == head:
1018 # All published commits are merged, and thus we are a
1019 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001020 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001021 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001022 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001023 self._CopyFiles()
1024 syncbuf.later1(self, _doff)
1025 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001027 # Examine the local commits not in the remote. Find the
1028 # last one attributed to this user, if any.
1029 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001030 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001031 last_mine = None
1032 cnt_mine = 0
1033 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001034 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001035 if committer_email == self.UserEmail:
1036 last_mine = commit_id
1037 cnt_mine += 1
1038
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001039 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001040 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041
1042 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001043 syncbuf.fail(self, _DirtyError())
1044 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001046 # If the upstream switched on us, warn the user.
1047 #
1048 if branch.merge != self.revisionExpr:
1049 if branch.merge and self.revisionExpr:
1050 syncbuf.info(self,
1051 'manifest switched %s...%s',
1052 branch.merge,
1053 self.revisionExpr)
1054 elif branch.merge:
1055 syncbuf.info(self,
1056 'manifest no longer tracks %s',
1057 branch.merge)
1058
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001059 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001061 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001063 syncbuf.info(self,
1064 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001065 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001067 branch.remote = self.GetRemote(self.remote.name)
1068 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 branch.Save()
1070
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001071 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001072 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001073 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001074 self._CopyFiles()
1075 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001076 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001078 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001079 self._CopyFiles()
1080 except GitError, e:
1081 syncbuf.fail(self, e)
1082 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001085 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001086 self._CopyFiles()
1087 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001089 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 # dest should already be an absolute path, but src is project relative
1091 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001092 abssrc = os.path.join(self.worktree, src)
1093 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001095 def DownloadPatchSet(self, change_id, patch_id):
1096 """Download a single patch set of a single change to FETCH_HEAD.
1097 """
1098 remote = self.GetRemote(self.remote.name)
1099
1100 cmd = ['fetch', remote.name]
1101 cmd.append('refs/changes/%2.2d/%d/%d' \
1102 % (change_id % 100, change_id, patch_id))
1103 cmd.extend(map(lambda x: str(x), remote.fetch))
1104 if GitCommand(self, cmd, bare=True).Wait() != 0:
1105 return None
1106 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001107 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001108 change_id,
1109 patch_id,
1110 self.bare_git.rev_parse('FETCH_HEAD'))
1111
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112
1113## Branch Management ##
1114
1115 def StartBranch(self, name):
1116 """Create a new branch off the manifest's revision.
1117 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001118 head = self.work_git.GetHead()
1119 if head == (R_HEADS + name):
1120 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001122 all = self.bare_ref.all
1123 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001124 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001125 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001126 capture_stdout = True,
1127 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001128
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001129 branch = self.GetBranch(name)
1130 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001131 branch.merge = self.revisionExpr
1132 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001133
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001134 if head.startswith(R_HEADS):
1135 try:
1136 head = all[head]
1137 except KeyError:
1138 head = None
1139
1140 if revid and head and revid == head:
1141 ref = os.path.join(self.gitdir, R_HEADS + name)
1142 try:
1143 os.makedirs(os.path.dirname(ref))
1144 except OSError:
1145 pass
1146 _lwrite(ref, '%s\n' % revid)
1147 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1148 'ref: %s%s\n' % (R_HEADS, name))
1149 branch.Save()
1150 return True
1151
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001152 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001154 capture_stdout = True,
1155 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001156 branch.Save()
1157 return True
1158 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159
Wink Saville02d79452009-04-10 13:01:24 -07001160 def CheckoutBranch(self, name):
1161 """Checkout a local topic branch.
1162 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001163 rev = R_HEADS + name
1164 head = self.work_git.GetHead()
1165 if head == rev:
1166 # Already on the branch
1167 #
1168 return True
Wink Saville02d79452009-04-10 13:01:24 -07001169
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001170 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001171 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001172 revid = all[rev]
1173 except KeyError:
1174 # Branch does not exist in this project
1175 #
1176 return False
Wink Saville02d79452009-04-10 13:01:24 -07001177
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001178 if head.startswith(R_HEADS):
1179 try:
1180 head = all[head]
1181 except KeyError:
1182 head = None
1183
1184 if head == revid:
1185 # Same revision; just update HEAD to point to the new
1186 # target branch, but otherwise take no other action.
1187 #
1188 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1189 'ref: %s%s\n' % (R_HEADS, name))
1190 return True
1191
1192 return GitCommand(self,
1193 ['checkout', name, '--'],
1194 capture_stdout = True,
1195 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001196
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001197 def AbandonBranch(self, name):
1198 """Destroy a local topic branch.
1199 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001200 rev = R_HEADS + name
1201 all = self.bare_ref.all
1202 if rev not in all:
1203 # Doesn't exist; assume already abandoned.
1204 #
1205 return True
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001206
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001207 head = self.work_git.GetHead()
1208 if head == rev:
1209 # We can't destroy the branch while we are sitting
1210 # on it. Switch to a detached HEAD.
1211 #
1212 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001213
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 revid = self.GetRevisionId(all)
1215 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001216 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1217 '%s\n' % revid)
1218 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001219 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001220
1221 return GitCommand(self,
1222 ['branch', '-D', name],
1223 capture_stdout = True,
1224 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001225
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 def PruneHeads(self):
1227 """Prune any topic branches already merged into upstream.
1228 """
1229 cb = self.CurrentBranch
1230 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001231 left = self._allrefs
1232 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233 if name.startswith(R_HEADS):
1234 name = name[len(R_HEADS):]
1235 if cb is None or name != cb:
1236 kill.append(name)
1237
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001238 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 if cb is not None \
1240 and not self._revlist(HEAD + '...' + rev) \
1241 and not self.IsDirty(consider_untracked = False):
1242 self.work_git.DetachHead(HEAD)
1243 kill.append(cb)
1244
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001246 old = self.bare_git.GetHead()
1247 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1249
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001250 try:
1251 self.bare_git.DetachHead(rev)
1252
1253 b = ['branch', '-d']
1254 b.extend(kill)
1255 b = GitCommand(self, b, bare=True,
1256 capture_stdout=True,
1257 capture_stderr=True)
1258 b.Wait()
1259 finally:
1260 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001261 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001263 for branch in kill:
1264 if (R_HEADS + branch) not in left:
1265 self.CleanPublishedCache()
1266 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267
1268 if cb and cb not in kill:
1269 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001270 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271
1272 kept = []
1273 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001274 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275 branch = self.GetBranch(branch)
1276 base = branch.LocalMerge
1277 if not base:
1278 base = rev
1279 kept.append(ReviewableBranch(self, branch, base))
1280 return kept
1281
1282
1283## Direct Git Commands ##
1284
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001285 def _RemoteFetch(self, name=None, tag=None,
1286 initial=False,
1287 quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288 if not name:
1289 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001290
1291 ssh_proxy = False
1292 if self.GetRemote(name).PreConnectFetch():
1293 ssh_proxy = True
1294
Shawn O. Pearce88443382010-10-08 10:02:09 +02001295 if initial:
1296 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1297 try:
1298 fd = open(alt, 'rb')
1299 try:
1300 ref_dir = fd.readline()
1301 if ref_dir and ref_dir.endswith('\n'):
1302 ref_dir = ref_dir[:-1]
1303 finally:
1304 fd.close()
1305 except IOError, e:
1306 ref_dir = None
1307
1308 if ref_dir and 'objects' == os.path.basename(ref_dir):
1309 ref_dir = os.path.dirname(ref_dir)
1310 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1311 remote = self.GetRemote(name)
1312
1313 all = self.bare_ref.all
1314 ids = set(all.values())
1315 tmp = set()
1316
1317 for r, id in GitRefs(ref_dir).all.iteritems():
1318 if r not in all:
1319 if r.startswith(R_TAGS) or remote.WritesTo(r):
1320 all[r] = id
1321 ids.add(id)
1322 continue
1323
1324 if id in ids:
1325 continue
1326
1327 r = 'refs/_alt/%s' % id
1328 all[r] = id
1329 ids.add(id)
1330 tmp.add(r)
1331
1332 ref_names = list(all.keys())
1333 ref_names.sort()
1334
1335 tmp_packed = ''
1336 old_packed = ''
1337
1338 for r in ref_names:
1339 line = '%s %s\n' % (all[r], r)
1340 tmp_packed += line
1341 if r not in tmp:
1342 old_packed += line
1343
1344 _lwrite(packed_refs, tmp_packed)
1345
1346 else:
1347 ref_dir = None
1348
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001349 cmd = ['fetch']
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001350 if quiet:
1351 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001352 if not self.worktree:
1353 cmd.append('--update-head-ok')
1354 cmd.append(name)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001355 if tag is not None:
1356 cmd.append('tag')
1357 cmd.append(tag)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001358
1359 ok = GitCommand(self,
1360 cmd,
1361 bare = True,
1362 ssh_proxy = ssh_proxy).Wait() == 0
1363
1364 if initial:
1365 if ref_dir:
1366 if old_packed != '':
1367 _lwrite(packed_refs, old_packed)
1368 else:
1369 os.remove(packed_refs)
1370 self.bare_git.pack_refs('--all', '--prune')
1371
1372 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
1374 def _Checkout(self, rev, quiet=False):
1375 cmd = ['checkout']
1376 if quiet:
1377 cmd.append('-q')
1378 cmd.append(rev)
1379 cmd.append('--')
1380 if GitCommand(self, cmd).Wait() != 0:
1381 if self._allrefs:
1382 raise GitError('%s checkout %s ' % (self.name, rev))
1383
1384 def _ResetHard(self, rev, quiet=True):
1385 cmd = ['reset', '--hard']
1386 if quiet:
1387 cmd.append('-q')
1388 cmd.append(rev)
1389 if GitCommand(self, cmd).Wait() != 0:
1390 raise GitError('%s reset --hard %s ' % (self.name, rev))
1391
1392 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001393 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 if onto is not None:
1395 cmd.extend(['--onto', onto])
1396 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001397 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398 raise GitError('%s rebase %s ' % (self.name, upstream))
1399
1400 def _FastForward(self, head):
1401 cmd = ['merge', head]
1402 if GitCommand(self, cmd).Wait() != 0:
1403 raise GitError('%s merge %s ' % (self.name, head))
1404
1405 def _InitGitDir(self):
1406 if not os.path.exists(self.gitdir):
1407 os.makedirs(self.gitdir)
1408 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001409
Shawn O. Pearce88443382010-10-08 10:02:09 +02001410 mp = self.manifest.manifestProject
1411 ref_dir = mp.config.GetString('repo.reference')
1412
1413 if ref_dir:
1414 mirror_git = os.path.join(ref_dir, self.name + '.git')
1415 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1416 self.relpath + '.git')
1417
1418 if os.path.exists(mirror_git):
1419 ref_dir = mirror_git
1420
1421 elif os.path.exists(repo_git):
1422 ref_dir = repo_git
1423
1424 else:
1425 ref_dir = None
1426
1427 if ref_dir:
1428 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1429 os.path.join(ref_dir, 'objects') + '\n')
1430
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001431 if self.manifest.IsMirror:
1432 self.config.SetString('core.bare', 'true')
1433 else:
1434 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435
1436 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001437 try:
1438 to_rm = os.listdir(hooks)
1439 except OSError:
1440 to_rm = []
1441 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001442 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001443 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001444
1445 m = self.manifest.manifestProject.config
1446 for key in ['user.name', 'user.email']:
1447 if m.Has(key, include_defaults = False):
1448 self.config.SetString(key, m.GetString(key))
1449
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001450 def _InitHooks(self):
1451 hooks = self._gitdir_path('hooks')
1452 if not os.path.exists(hooks):
1453 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001454 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001455 name = os.path.basename(stock_hook)
1456
Doug Anderson2536f802011-01-10 12:38:37 -08001457 if name in ('commit-msg',) and not self.remote.review:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001458 # Don't install a Gerrit Code Review hook if this
1459 # project does not appear to use it for reviews.
1460 #
1461 continue
1462
1463 dst = os.path.join(hooks, name)
1464 if os.path.islink(dst):
1465 continue
1466 if os.path.exists(dst):
1467 if filecmp.cmp(stock_hook, dst, shallow=False):
1468 os.remove(dst)
1469 else:
1470 _error("%s: Not replacing %s hook", self.relpath, name)
1471 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001472 try:
1473 os.symlink(relpath(stock_hook, dst), dst)
1474 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001475 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001476 raise GitError('filesystem must support symlinks')
1477 else:
1478 raise
1479
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001481 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001482 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001483 remote.url = self.remote.url
1484 remote.review = self.remote.review
1485 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001487 if self.worktree:
1488 remote.ResetFetch(mirror=False)
1489 else:
1490 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 remote.Save()
1492
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001493 def _InitMRef(self):
1494 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001495 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001496
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001497 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001498 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001499
1500 def _InitAnyMRef(self, ref):
1501 cur = self.bare_ref.symref(ref)
1502
1503 if self.revisionId:
1504 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1505 msg = 'manifest set to %s' % self.revisionId
1506 dst = self.revisionId + '^0'
1507 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1508 else:
1509 remote = self.GetRemote(self.remote.name)
1510 dst = remote.ToLocal(self.revisionExpr)
1511 if cur != dst:
1512 msg = 'manifest set to %s' % self.revisionExpr
1513 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001514
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515 def _InitWorkTree(self):
1516 dotgit = os.path.join(self.worktree, '.git')
1517 if not os.path.exists(dotgit):
1518 os.makedirs(dotgit)
1519
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001520 for name in ['config',
1521 'description',
1522 'hooks',
1523 'info',
1524 'logs',
1525 'objects',
1526 'packed-refs',
1527 'refs',
1528 'rr-cache',
1529 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001530 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001531 src = os.path.join(self.gitdir, name)
1532 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001533 if os.path.islink(dst) or not os.path.exists(dst):
1534 os.symlink(relpath(src, dst), dst)
1535 else:
1536 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001537 except OSError, e:
1538 if e.errno == errno.EPERM:
1539 raise GitError('filesystem must support symlinks')
1540 else:
1541 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001542
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001543 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001544
1545 cmd = ['read-tree', '--reset', '-u']
1546 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001547 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548 if GitCommand(self, cmd).Wait() != 0:
1549 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001550
1551 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1552 if not os.path.exists(rr_cache):
1553 os.makedirs(rr_cache)
1554
Shawn O. Pearce93609662009-04-21 10:50:33 -07001555 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556
1557 def _gitdir_path(self, path):
1558 return os.path.join(self.gitdir, path)
1559
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001560 def _revlist(self, *args, **kw):
1561 a = []
1562 a.extend(args)
1563 a.append('--')
1564 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565
1566 @property
1567 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001568 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569
1570 class _GitGetByExec(object):
1571 def __init__(self, project, bare):
1572 self._project = project
1573 self._bare = bare
1574
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001575 def LsOthers(self):
1576 p = GitCommand(self._project,
1577 ['ls-files',
1578 '-z',
1579 '--others',
1580 '--exclude-standard'],
1581 bare = False,
1582 capture_stdout = True,
1583 capture_stderr = True)
1584 if p.Wait() == 0:
1585 out = p.stdout
1586 if out:
1587 return out[:-1].split("\0")
1588 return []
1589
1590 def DiffZ(self, name, *args):
1591 cmd = [name]
1592 cmd.append('-z')
1593 cmd.extend(args)
1594 p = GitCommand(self._project,
1595 cmd,
1596 bare = False,
1597 capture_stdout = True,
1598 capture_stderr = True)
1599 try:
1600 out = p.process.stdout.read()
1601 r = {}
1602 if out:
1603 out = iter(out[:-1].split('\0'))
1604 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001605 try:
1606 info = out.next()
1607 path = out.next()
1608 except StopIteration:
1609 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610
1611 class _Info(object):
1612 def __init__(self, path, omode, nmode, oid, nid, state):
1613 self.path = path
1614 self.src_path = None
1615 self.old_mode = omode
1616 self.new_mode = nmode
1617 self.old_id = oid
1618 self.new_id = nid
1619
1620 if len(state) == 1:
1621 self.status = state
1622 self.level = None
1623 else:
1624 self.status = state[:1]
1625 self.level = state[1:]
1626 while self.level.startswith('0'):
1627 self.level = self.level[1:]
1628
1629 info = info[1:].split(' ')
1630 info =_Info(path, *info)
1631 if info.status in ('R', 'C'):
1632 info.src_path = info.path
1633 info.path = out.next()
1634 r[info.path] = info
1635 return r
1636 finally:
1637 p.Wait()
1638
1639 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001640 if self._bare:
1641 path = os.path.join(self._project.gitdir, HEAD)
1642 else:
1643 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001644 fd = open(path, 'rb')
1645 try:
1646 line = fd.read()
1647 finally:
1648 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001649 if line.startswith('ref: '):
1650 return line[5:-1]
1651 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652
1653 def SetHead(self, ref, message=None):
1654 cmdv = []
1655 if message is not None:
1656 cmdv.extend(['-m', message])
1657 cmdv.append(HEAD)
1658 cmdv.append(ref)
1659 self.symbolic_ref(*cmdv)
1660
1661 def DetachHead(self, new, message=None):
1662 cmdv = ['--no-deref']
1663 if message is not None:
1664 cmdv.extend(['-m', message])
1665 cmdv.append(HEAD)
1666 cmdv.append(new)
1667 self.update_ref(*cmdv)
1668
1669 def UpdateRef(self, name, new, old=None,
1670 message=None,
1671 detach=False):
1672 cmdv = []
1673 if message is not None:
1674 cmdv.extend(['-m', message])
1675 if detach:
1676 cmdv.append('--no-deref')
1677 cmdv.append(name)
1678 cmdv.append(new)
1679 if old is not None:
1680 cmdv.append(old)
1681 self.update_ref(*cmdv)
1682
1683 def DeleteRef(self, name, old=None):
1684 if not old:
1685 old = self.rev_parse(name)
1686 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001687 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001689 def rev_list(self, *args, **kw):
1690 if 'format' in kw:
1691 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1692 else:
1693 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 cmdv.extend(args)
1695 p = GitCommand(self._project,
1696 cmdv,
1697 bare = self._bare,
1698 capture_stdout = True,
1699 capture_stderr = True)
1700 r = []
1701 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001702 if line[-1] == '\n':
1703 line = line[:-1]
1704 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 if p.Wait() != 0:
1706 raise GitError('%s rev-list %s: %s' % (
1707 self._project.name,
1708 str(args),
1709 p.stderr))
1710 return r
1711
1712 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001713 """Allow arbitrary git commands using pythonic syntax.
1714
1715 This allows you to do things like:
1716 git_obj.rev_parse('HEAD')
1717
1718 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1719 run. We'll replace the '_' with a '-' and try to run a git command.
1720 Any other arguments will be passed to the git command.
1721
1722 Args:
1723 name: The name of the git command to call. Any '_' characters will
1724 be replaced with '-'.
1725
1726 Returns:
1727 A callable object that will try to call git with the named command.
1728 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729 name = name.replace('_', '-')
1730 def runner(*args):
1731 cmdv = [name]
1732 cmdv.extend(args)
1733 p = GitCommand(self._project,
1734 cmdv,
1735 bare = self._bare,
1736 capture_stdout = True,
1737 capture_stderr = True)
1738 if p.Wait() != 0:
1739 raise GitError('%s %s: %s' % (
1740 self._project.name,
1741 name,
1742 p.stderr))
1743 r = p.stdout
1744 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1745 return r[:-1]
1746 return r
1747 return runner
1748
1749
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001750class _PriorSyncFailedError(Exception):
1751 def __str__(self):
1752 return 'prior sync failed; rebase still in progress'
1753
1754class _DirtyError(Exception):
1755 def __str__(self):
1756 return 'contains uncommitted changes'
1757
1758class _InfoMessage(object):
1759 def __init__(self, project, text):
1760 self.project = project
1761 self.text = text
1762
1763 def Print(self, syncbuf):
1764 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1765 syncbuf.out.nl()
1766
1767class _Failure(object):
1768 def __init__(self, project, why):
1769 self.project = project
1770 self.why = why
1771
1772 def Print(self, syncbuf):
1773 syncbuf.out.fail('error: %s/: %s',
1774 self.project.relpath,
1775 str(self.why))
1776 syncbuf.out.nl()
1777
1778class _Later(object):
1779 def __init__(self, project, action):
1780 self.project = project
1781 self.action = action
1782
1783 def Run(self, syncbuf):
1784 out = syncbuf.out
1785 out.project('project %s/', self.project.relpath)
1786 out.nl()
1787 try:
1788 self.action()
1789 out.nl()
1790 return True
1791 except GitError, e:
1792 out.nl()
1793 return False
1794
1795class _SyncColoring(Coloring):
1796 def __init__(self, config):
1797 Coloring.__init__(self, config, 'reposync')
1798 self.project = self.printer('header', attr = 'bold')
1799 self.info = self.printer('info')
1800 self.fail = self.printer('fail', fg='red')
1801
1802class SyncBuffer(object):
1803 def __init__(self, config, detach_head=False):
1804 self._messages = []
1805 self._failures = []
1806 self._later_queue1 = []
1807 self._later_queue2 = []
1808
1809 self.out = _SyncColoring(config)
1810 self.out.redirect(sys.stderr)
1811
1812 self.detach_head = detach_head
1813 self.clean = True
1814
1815 def info(self, project, fmt, *args):
1816 self._messages.append(_InfoMessage(project, fmt % args))
1817
1818 def fail(self, project, err=None):
1819 self._failures.append(_Failure(project, err))
1820 self.clean = False
1821
1822 def later1(self, project, what):
1823 self._later_queue1.append(_Later(project, what))
1824
1825 def later2(self, project, what):
1826 self._later_queue2.append(_Later(project, what))
1827
1828 def Finish(self):
1829 self._PrintMessages()
1830 self._RunLater()
1831 self._PrintMessages()
1832 return self.clean
1833
1834 def _RunLater(self):
1835 for q in ['_later_queue1', '_later_queue2']:
1836 if not self._RunQueue(q):
1837 return
1838
1839 def _RunQueue(self, queue):
1840 for m in getattr(self, queue):
1841 if not m.Run(self):
1842 self.clean = False
1843 return False
1844 setattr(self, queue, [])
1845 return True
1846
1847 def _PrintMessages(self):
1848 for m in self._messages:
1849 m.Print(self)
1850 for m in self._failures:
1851 m.Print(self)
1852
1853 self._messages = []
1854 self._failures = []
1855
1856
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001857class MetaProject(Project):
1858 """A special project housed under .repo.
1859 """
1860 def __init__(self, manifest, name, gitdir, worktree):
1861 repodir = manifest.repodir
1862 Project.__init__(self,
1863 manifest = manifest,
1864 name = name,
1865 gitdir = gitdir,
1866 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001867 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001868 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001869 revisionExpr = 'refs/heads/master',
1870 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001871
1872 def PreSync(self):
1873 if self.Exists:
1874 cb = self.CurrentBranch
1875 if cb:
1876 base = self.GetBranch(cb).merge
1877 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001878 self.revisionExpr = base
1879 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001880
1881 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07001882 def LastFetch(self):
1883 try:
1884 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
1885 return os.path.getmtime(fh)
1886 except OSError:
1887 return 0
1888
1889 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890 def HasChanges(self):
1891 """Has the remote received new commits not yet checked out?
1892 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001893 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001894 return False
1895
1896 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001897 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001898 head = self.work_git.GetHead()
1899 if head.startswith(R_HEADS):
1900 try:
1901 head = all[head]
1902 except KeyError:
1903 head = None
1904
1905 if revid == head:
1906 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001907 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001908 return True
1909 return False