blob: 6252bd68ba11f333e631c6acf51fe3739434356b [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
Terence Haddock4655e812011-03-31 12:33:34 +0200653 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200655
656 Args:
657 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700658 """
659 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200660 if output_redir == None:
661 output_redir = sys.stdout
662 print >>output_redir, ''
663 print >>output_redir, 'project %s/' % self.relpath
664 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700665 return
666
667 self.work_git.update_index('-q',
668 '--unmerged',
669 '--ignore-missing',
670 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700671 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
673 df = self.work_git.DiffZ('diff-files')
674 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700675 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700676 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677
678 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200679 if not output_redir == None:
680 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681 out.project('project %-40s', self.relpath + '/')
682
683 branch = self.CurrentBranch
684 if branch is None:
685 out.nobranch('(*** NO BRANCH ***)')
686 else:
687 out.branch('branch %s', branch)
688 out.nl()
689
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700690 if rb:
691 out.important('prior sync failed; rebase still in progress')
692 out.nl()
693
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694 paths = list()
695 paths.extend(di.keys())
696 paths.extend(df.keys())
697 paths.extend(do)
698
699 paths = list(set(paths))
700 paths.sort()
701
702 for p in paths:
703 try: i = di[p]
704 except KeyError: i = None
705
706 try: f = df[p]
707 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200708
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700709 if i: i_status = i.status.upper()
710 else: i_status = '-'
711
712 if f: f_status = f.status.lower()
713 else: f_status = '-'
714
715 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800716 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 i.src_path, p, i.level)
718 else:
719 line = ' %s%s\t%s' % (i_status, f_status, p)
720
721 if i and not f:
722 out.added('%s', line)
723 elif (i and f) or (not i and f):
724 out.changed('%s', line)
725 elif not i and not f:
726 out.untracked('%s', line)
727 else:
728 out.write('%s', line)
729 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200730
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700731 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
733 def PrintWorkTreeDiff(self):
734 """Prints the status of the repository to stdout.
735 """
736 out = DiffColoring(self.config)
737 cmd = ['diff']
738 if out.is_on:
739 cmd.append('--color')
740 cmd.append(HEAD)
741 cmd.append('--')
742 p = GitCommand(self,
743 cmd,
744 capture_stdout = True,
745 capture_stderr = True)
746 has_diff = False
747 for line in p.process.stdout:
748 if not has_diff:
749 out.nl()
750 out.project('project %s/' % self.relpath)
751 out.nl()
752 has_diff = True
753 print line[:-1]
754 p.Wait()
755
756
757## Publish / Upload ##
758
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700759 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760 """Was the branch published (uploaded) for code review?
761 If so, returns the SHA-1 hash of the last published
762 state for the branch.
763 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700764 key = R_PUB + branch
765 if all is None:
766 try:
767 return self.bare_git.rev_parse(key)
768 except GitError:
769 return None
770 else:
771 try:
772 return all[key]
773 except KeyError:
774 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700776 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 """Prunes any stale published refs.
778 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700779 if all is None:
780 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781 heads = set()
782 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700783 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 if name.startswith(R_HEADS):
785 heads.add(name)
786 elif name.startswith(R_PUB):
787 canrm[name] = id
788
789 for name, id in canrm.iteritems():
790 n = name[len(R_PUB):]
791 if R_HEADS + n not in heads:
792 self.bare_git.DeleteRef(name, id)
793
794 def GetUploadableBranches(self):
795 """List any branches which can be uploaded for review.
796 """
797 heads = {}
798 pubed = {}
799
800 for name, id in self._allrefs.iteritems():
801 if name.startswith(R_HEADS):
802 heads[name[len(R_HEADS):]] = id
803 elif name.startswith(R_PUB):
804 pubed[name[len(R_PUB):]] = id
805
806 ready = []
807 for branch, id in heads.iteritems():
808 if branch in pubed and pubed[branch] == id:
809 continue
810
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800811 rb = self.GetUploadableBranch(branch)
812 if rb:
813 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 return ready
815
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800816 def GetUploadableBranch(self, branch_name):
817 """Get a single uploadable branch, or None.
818 """
819 branch = self.GetBranch(branch_name)
820 base = branch.LocalMerge
821 if branch.LocalMerge:
822 rb = ReviewableBranch(self, branch, base)
823 if rb.commits:
824 return rb
825 return None
826
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700827 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700828 people=([],[]),
829 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 """Uploads the named branch for code review.
831 """
832 if branch is None:
833 branch = self.CurrentBranch
834 if branch is None:
835 raise GitError('not currently on a branch')
836
837 branch = self.GetBranch(branch)
838 if not branch.LocalMerge:
839 raise GitError('branch %s does not track a remote' % branch.name)
840 if not branch.remote.review:
841 raise GitError('remote %s has no review url' % branch.remote.name)
842
843 dest_branch = branch.merge
844 if not dest_branch.startswith(R_HEADS):
845 dest_branch = R_HEADS + dest_branch
846
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800847 if not branch.remote.projectname:
848 branch.remote.projectname = self.name
849 branch.remote.Save()
850
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800851 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800852 if dest_branch.startswith(R_HEADS):
853 dest_branch = dest_branch[len(R_HEADS):]
854
855 rp = ['gerrit receive-pack']
856 for e in people[0]:
857 rp.append('--reviewer=%s' % sq(e))
858 for e in people[1]:
859 rp.append('--cc=%s' % sq(e))
860
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700861 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
862 if auto_topic:
863 ref_spec = ref_spec + '/' + branch.name
864
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800865 cmd = ['push']
866 cmd.append('--receive-pack=%s' % " ".join(rp))
867 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700868 cmd.append(ref_spec)
869
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800870 if GitCommand(self, cmd, bare = True).Wait() != 0:
871 raise UploadError('Upload failed')
872
873 else:
874 raise UploadError('Unsupported protocol %s' \
875 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876
877 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
878 self.bare_git.UpdateRef(R_PUB + branch.name,
879 R_HEADS + branch.name,
880 message = msg)
881
882
883## Sync ##
884
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700885 def Sync_NetworkHalf(self, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 """Perform only the network IO portion of the sync process.
887 Local working directory/branch state is not affected.
888 """
Shawn O. Pearce88443382010-10-08 10:02:09 +0200889 is_new = not self.Exists
890 if is_new:
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700891 if not quiet:
892 print >>sys.stderr
893 print >>sys.stderr, 'Initializing project %s ...' % self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800895
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 self._InitRemote()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700897 if not self._RemoteFetch(initial=is_new, quiet=quiet):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800899
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200900 #Check that the requested ref was found after fetch
901 #
902 try:
903 self.GetRevisionId()
904 except ManifestInvalidRevisionError:
905 # if the ref is a tag. We can try fetching
906 # the tag manually as a last resort
907 #
908 rev = self.revisionExpr
909 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700910 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200911
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800912 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800913 self._InitMRef()
914 else:
915 self._InitMirrorHead()
916 try:
917 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
918 except OSError:
919 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800921
922 def PostRepoUpgrade(self):
923 self._InitHooks()
924
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 def _CopyFiles(self):
926 for file in self.copyfiles:
927 file._Copy()
928
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700929 def GetRevisionId(self, all=None):
930 if self.revisionId:
931 return self.revisionId
932
933 rem = self.GetRemote(self.remote.name)
934 rev = rem.ToLocal(self.revisionExpr)
935
936 if all is not None and rev in all:
937 return all[rev]
938
939 try:
940 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
941 except GitError:
942 raise ManifestInvalidRevisionError(
943 'revision %s in %s not found' % (self.revisionExpr,
944 self.name))
945
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700946 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 """Perform only the local IO portion of the sync process.
948 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700950 all = self.bare_ref.all
951 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700952 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800953
954 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700955 head = self.work_git.GetHead()
956 if head.startswith(R_HEADS):
957 branch = head[len(R_HEADS):]
958 try:
959 head = all[head]
960 except KeyError:
961 head = None
962 else:
963 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700965 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 # Currently on a detached HEAD. The user is assumed to
967 # not have any local modifications worth worrying about.
968 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700969 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700970 syncbuf.fail(self, _PriorSyncFailedError())
971 return
972
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700973 if head == revid:
974 # No changes; don't do anything further.
975 #
976 return
977
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700978 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700980 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700982 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700983 except GitError, e:
984 syncbuf.fail(self, e)
985 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700987 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700989 if head == revid:
990 # No changes; don't do anything further.
991 #
992 return
993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700996 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 # The current branch has no tracking configuration.
998 # Jump off it to a deatched HEAD.
999 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001000 syncbuf.info(self,
1001 "leaving %s; does not track upstream",
1002 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001004 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001005 except GitError, e:
1006 syncbuf.fail(self, e)
1007 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001009 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001011 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001012 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001014 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 if not_merged:
1016 if upstream_gain:
1017 # The user has published this branch and some of those
1018 # commits are not yet merged upstream. We do not want
1019 # to rewrite the published commits so we punt.
1020 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001021 syncbuf.fail(self,
1022 "branch %s is published (but not merged) and is now %d commits behind"
1023 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001024 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001025 elif pub == head:
1026 # All published commits are merged, and thus we are a
1027 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001028 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001029 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001030 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001031 self._CopyFiles()
1032 syncbuf.later1(self, _doff)
1033 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001035 # Examine the local commits not in the remote. Find the
1036 # last one attributed to this user, if any.
1037 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001038 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001039 last_mine = None
1040 cnt_mine = 0
1041 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001042 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001043 if committer_email == self.UserEmail:
1044 last_mine = commit_id
1045 cnt_mine += 1
1046
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001047 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001048 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049
1050 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001051 syncbuf.fail(self, _DirtyError())
1052 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001054 # If the upstream switched on us, warn the user.
1055 #
1056 if branch.merge != self.revisionExpr:
1057 if branch.merge and self.revisionExpr:
1058 syncbuf.info(self,
1059 'manifest switched %s...%s',
1060 branch.merge,
1061 self.revisionExpr)
1062 elif branch.merge:
1063 syncbuf.info(self,
1064 'manifest no longer tracks %s',
1065 branch.merge)
1066
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001067 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001069 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 syncbuf.info(self,
1072 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001073 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001075 branch.remote = self.GetRemote(self.remote.name)
1076 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 branch.Save()
1078
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001079 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001081 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 self._CopyFiles()
1083 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001084 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001087 self._CopyFiles()
1088 except GitError, e:
1089 syncbuf.fail(self, e)
1090 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001092 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001093 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001094 self._CopyFiles()
1095 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001097 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098 # dest should already be an absolute path, but src is project relative
1099 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001100 abssrc = os.path.join(self.worktree, src)
1101 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001103 def DownloadPatchSet(self, change_id, patch_id):
1104 """Download a single patch set of a single change to FETCH_HEAD.
1105 """
1106 remote = self.GetRemote(self.remote.name)
1107
1108 cmd = ['fetch', remote.name]
1109 cmd.append('refs/changes/%2.2d/%d/%d' \
1110 % (change_id % 100, change_id, patch_id))
1111 cmd.extend(map(lambda x: str(x), remote.fetch))
1112 if GitCommand(self, cmd, bare=True).Wait() != 0:
1113 return None
1114 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001115 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001116 change_id,
1117 patch_id,
1118 self.bare_git.rev_parse('FETCH_HEAD'))
1119
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120
1121## Branch Management ##
1122
1123 def StartBranch(self, name):
1124 """Create a new branch off the manifest's revision.
1125 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001126 head = self.work_git.GetHead()
1127 if head == (R_HEADS + name):
1128 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001130 all = self.bare_ref.all
1131 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001132 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001133 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001134 capture_stdout = True,
1135 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001136
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001137 branch = self.GetBranch(name)
1138 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001139 branch.merge = self.revisionExpr
1140 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001141
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001142 if head.startswith(R_HEADS):
1143 try:
1144 head = all[head]
1145 except KeyError:
1146 head = None
1147
1148 if revid and head and revid == head:
1149 ref = os.path.join(self.gitdir, R_HEADS + name)
1150 try:
1151 os.makedirs(os.path.dirname(ref))
1152 except OSError:
1153 pass
1154 _lwrite(ref, '%s\n' % revid)
1155 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1156 'ref: %s%s\n' % (R_HEADS, name))
1157 branch.Save()
1158 return True
1159
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001160 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001161 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001162 capture_stdout = True,
1163 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001164 branch.Save()
1165 return True
1166 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
Wink Saville02d79452009-04-10 13:01:24 -07001168 def CheckoutBranch(self, name):
1169 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001170
1171 Args:
1172 name: The name of the branch to checkout.
1173
1174 Returns:
1175 True if the checkout succeeded; False if it didn't; None if the branch
1176 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001177 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001178 rev = R_HEADS + name
1179 head = self.work_git.GetHead()
1180 if head == rev:
1181 # Already on the branch
1182 #
1183 return True
Wink Saville02d79452009-04-10 13:01:24 -07001184
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001185 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001186 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001187 revid = all[rev]
1188 except KeyError:
1189 # Branch does not exist in this project
1190 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001191 return None
Wink Saville02d79452009-04-10 13:01:24 -07001192
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001193 if head.startswith(R_HEADS):
1194 try:
1195 head = all[head]
1196 except KeyError:
1197 head = None
1198
1199 if head == revid:
1200 # Same revision; just update HEAD to point to the new
1201 # target branch, but otherwise take no other action.
1202 #
1203 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1204 'ref: %s%s\n' % (R_HEADS, name))
1205 return True
1206
1207 return GitCommand(self,
1208 ['checkout', name, '--'],
1209 capture_stdout = True,
1210 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001211
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001212 def AbandonBranch(self, name):
1213 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001214
1215 Args:
1216 name: The name of the branch to abandon.
1217
1218 Returns:
1219 True if the abandon succeeded; False if it didn't; None if the branch
1220 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001221 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001222 rev = R_HEADS + name
1223 all = self.bare_ref.all
1224 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001225 # Doesn't exist
1226 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001227
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001228 head = self.work_git.GetHead()
1229 if head == rev:
1230 # We can't destroy the branch while we are sitting
1231 # on it. Switch to a detached HEAD.
1232 #
1233 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001234
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001235 revid = self.GetRevisionId(all)
1236 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001237 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1238 '%s\n' % revid)
1239 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001241
1242 return GitCommand(self,
1243 ['branch', '-D', name],
1244 capture_stdout = True,
1245 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 def PruneHeads(self):
1248 """Prune any topic branches already merged into upstream.
1249 """
1250 cb = self.CurrentBranch
1251 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001252 left = self._allrefs
1253 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254 if name.startswith(R_HEADS):
1255 name = name[len(R_HEADS):]
1256 if cb is None or name != cb:
1257 kill.append(name)
1258
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001259 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 if cb is not None \
1261 and not self._revlist(HEAD + '...' + rev) \
1262 and not self.IsDirty(consider_untracked = False):
1263 self.work_git.DetachHead(HEAD)
1264 kill.append(cb)
1265
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001267 old = self.bare_git.GetHead()
1268 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001269 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1270
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 try:
1272 self.bare_git.DetachHead(rev)
1273
1274 b = ['branch', '-d']
1275 b.extend(kill)
1276 b = GitCommand(self, b, bare=True,
1277 capture_stdout=True,
1278 capture_stderr=True)
1279 b.Wait()
1280 finally:
1281 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001282 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001284 for branch in kill:
1285 if (R_HEADS + branch) not in left:
1286 self.CleanPublishedCache()
1287 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288
1289 if cb and cb not in kill:
1290 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001291 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
1293 kept = []
1294 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001295 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 branch = self.GetBranch(branch)
1297 base = branch.LocalMerge
1298 if not base:
1299 base = rev
1300 kept.append(ReviewableBranch(self, branch, base))
1301 return kept
1302
1303
1304## Direct Git Commands ##
1305
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001306 def _RemoteFetch(self, name=None, tag=None,
1307 initial=False,
1308 quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309 if not name:
1310 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001311
1312 ssh_proxy = False
1313 if self.GetRemote(name).PreConnectFetch():
1314 ssh_proxy = True
1315
Shawn O. Pearce88443382010-10-08 10:02:09 +02001316 if initial:
1317 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1318 try:
1319 fd = open(alt, 'rb')
1320 try:
1321 ref_dir = fd.readline()
1322 if ref_dir and ref_dir.endswith('\n'):
1323 ref_dir = ref_dir[:-1]
1324 finally:
1325 fd.close()
1326 except IOError, e:
1327 ref_dir = None
1328
1329 if ref_dir and 'objects' == os.path.basename(ref_dir):
1330 ref_dir = os.path.dirname(ref_dir)
1331 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1332 remote = self.GetRemote(name)
1333
1334 all = self.bare_ref.all
1335 ids = set(all.values())
1336 tmp = set()
1337
1338 for r, id in GitRefs(ref_dir).all.iteritems():
1339 if r not in all:
1340 if r.startswith(R_TAGS) or remote.WritesTo(r):
1341 all[r] = id
1342 ids.add(id)
1343 continue
1344
1345 if id in ids:
1346 continue
1347
1348 r = 'refs/_alt/%s' % id
1349 all[r] = id
1350 ids.add(id)
1351 tmp.add(r)
1352
1353 ref_names = list(all.keys())
1354 ref_names.sort()
1355
1356 tmp_packed = ''
1357 old_packed = ''
1358
1359 for r in ref_names:
1360 line = '%s %s\n' % (all[r], r)
1361 tmp_packed += line
1362 if r not in tmp:
1363 old_packed += line
1364
1365 _lwrite(packed_refs, tmp_packed)
1366
1367 else:
1368 ref_dir = None
1369
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001370 cmd = ['fetch']
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001371 if quiet:
1372 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001373 if not self.worktree:
1374 cmd.append('--update-head-ok')
1375 cmd.append(name)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001376 if tag is not None:
1377 cmd.append('tag')
1378 cmd.append(tag)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001379
1380 ok = GitCommand(self,
1381 cmd,
1382 bare = True,
1383 ssh_proxy = ssh_proxy).Wait() == 0
1384
1385 if initial:
1386 if ref_dir:
1387 if old_packed != '':
1388 _lwrite(packed_refs, old_packed)
1389 else:
1390 os.remove(packed_refs)
1391 self.bare_git.pack_refs('--all', '--prune')
1392
1393 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394
1395 def _Checkout(self, rev, quiet=False):
1396 cmd = ['checkout']
1397 if quiet:
1398 cmd.append('-q')
1399 cmd.append(rev)
1400 cmd.append('--')
1401 if GitCommand(self, cmd).Wait() != 0:
1402 if self._allrefs:
1403 raise GitError('%s checkout %s ' % (self.name, rev))
1404
1405 def _ResetHard(self, rev, quiet=True):
1406 cmd = ['reset', '--hard']
1407 if quiet:
1408 cmd.append('-q')
1409 cmd.append(rev)
1410 if GitCommand(self, cmd).Wait() != 0:
1411 raise GitError('%s reset --hard %s ' % (self.name, rev))
1412
1413 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001414 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415 if onto is not None:
1416 cmd.extend(['--onto', onto])
1417 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001418 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419 raise GitError('%s rebase %s ' % (self.name, upstream))
1420
1421 def _FastForward(self, head):
1422 cmd = ['merge', head]
1423 if GitCommand(self, cmd).Wait() != 0:
1424 raise GitError('%s merge %s ' % (self.name, head))
1425
1426 def _InitGitDir(self):
1427 if not os.path.exists(self.gitdir):
1428 os.makedirs(self.gitdir)
1429 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001430
Shawn O. Pearce88443382010-10-08 10:02:09 +02001431 mp = self.manifest.manifestProject
1432 ref_dir = mp.config.GetString('repo.reference')
1433
1434 if ref_dir:
1435 mirror_git = os.path.join(ref_dir, self.name + '.git')
1436 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1437 self.relpath + '.git')
1438
1439 if os.path.exists(mirror_git):
1440 ref_dir = mirror_git
1441
1442 elif os.path.exists(repo_git):
1443 ref_dir = repo_git
1444
1445 else:
1446 ref_dir = None
1447
1448 if ref_dir:
1449 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1450 os.path.join(ref_dir, 'objects') + '\n')
1451
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001452 if self.manifest.IsMirror:
1453 self.config.SetString('core.bare', 'true')
1454 else:
1455 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456
1457 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001458 try:
1459 to_rm = os.listdir(hooks)
1460 except OSError:
1461 to_rm = []
1462 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001464 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465
1466 m = self.manifest.manifestProject.config
1467 for key in ['user.name', 'user.email']:
1468 if m.Has(key, include_defaults = False):
1469 self.config.SetString(key, m.GetString(key))
1470
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001471 def _InitHooks(self):
1472 hooks = self._gitdir_path('hooks')
1473 if not os.path.exists(hooks):
1474 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001475 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001476 name = os.path.basename(stock_hook)
1477
Doug Anderson2536f802011-01-10 12:38:37 -08001478 if name in ('commit-msg',) and not self.remote.review:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001479 # Don't install a Gerrit Code Review hook if this
1480 # project does not appear to use it for reviews.
1481 #
1482 continue
1483
1484 dst = os.path.join(hooks, name)
1485 if os.path.islink(dst):
1486 continue
1487 if os.path.exists(dst):
1488 if filecmp.cmp(stock_hook, dst, shallow=False):
1489 os.remove(dst)
1490 else:
1491 _error("%s: Not replacing %s hook", self.relpath, name)
1492 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001493 try:
1494 os.symlink(relpath(stock_hook, dst), dst)
1495 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001496 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001497 raise GitError('filesystem must support symlinks')
1498 else:
1499 raise
1500
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001501 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001502 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001504 remote.url = self.remote.url
1505 remote.review = self.remote.review
1506 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001507
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001508 if self.worktree:
1509 remote.ResetFetch(mirror=False)
1510 else:
1511 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512 remote.Save()
1513
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514 def _InitMRef(self):
1515 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001516 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001518 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001519 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001520
1521 def _InitAnyMRef(self, ref):
1522 cur = self.bare_ref.symref(ref)
1523
1524 if self.revisionId:
1525 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1526 msg = 'manifest set to %s' % self.revisionId
1527 dst = self.revisionId + '^0'
1528 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1529 else:
1530 remote = self.GetRemote(self.remote.name)
1531 dst = remote.ToLocal(self.revisionExpr)
1532 if cur != dst:
1533 msg = 'manifest set to %s' % self.revisionExpr
1534 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001535
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001536 def _InitWorkTree(self):
1537 dotgit = os.path.join(self.worktree, '.git')
1538 if not os.path.exists(dotgit):
1539 os.makedirs(dotgit)
1540
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001541 for name in ['config',
1542 'description',
1543 'hooks',
1544 'info',
1545 'logs',
1546 'objects',
1547 'packed-refs',
1548 'refs',
1549 'rr-cache',
1550 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001551 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001552 src = os.path.join(self.gitdir, name)
1553 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001554 if os.path.islink(dst) or not os.path.exists(dst):
1555 os.symlink(relpath(src, dst), dst)
1556 else:
1557 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001558 except OSError, e:
1559 if e.errno == errno.EPERM:
1560 raise GitError('filesystem must support symlinks')
1561 else:
1562 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001564 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565
1566 cmd = ['read-tree', '--reset', '-u']
1567 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001568 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 if GitCommand(self, cmd).Wait() != 0:
1570 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001571
1572 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1573 if not os.path.exists(rr_cache):
1574 os.makedirs(rr_cache)
1575
Shawn O. Pearce93609662009-04-21 10:50:33 -07001576 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577
1578 def _gitdir_path(self, path):
1579 return os.path.join(self.gitdir, path)
1580
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001581 def _revlist(self, *args, **kw):
1582 a = []
1583 a.extend(args)
1584 a.append('--')
1585 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001586
1587 @property
1588 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001589 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590
1591 class _GitGetByExec(object):
1592 def __init__(self, project, bare):
1593 self._project = project
1594 self._bare = bare
1595
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001596 def LsOthers(self):
1597 p = GitCommand(self._project,
1598 ['ls-files',
1599 '-z',
1600 '--others',
1601 '--exclude-standard'],
1602 bare = False,
1603 capture_stdout = True,
1604 capture_stderr = True)
1605 if p.Wait() == 0:
1606 out = p.stdout
1607 if out:
1608 return out[:-1].split("\0")
1609 return []
1610
1611 def DiffZ(self, name, *args):
1612 cmd = [name]
1613 cmd.append('-z')
1614 cmd.extend(args)
1615 p = GitCommand(self._project,
1616 cmd,
1617 bare = False,
1618 capture_stdout = True,
1619 capture_stderr = True)
1620 try:
1621 out = p.process.stdout.read()
1622 r = {}
1623 if out:
1624 out = iter(out[:-1].split('\0'))
1625 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001626 try:
1627 info = out.next()
1628 path = out.next()
1629 except StopIteration:
1630 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001631
1632 class _Info(object):
1633 def __init__(self, path, omode, nmode, oid, nid, state):
1634 self.path = path
1635 self.src_path = None
1636 self.old_mode = omode
1637 self.new_mode = nmode
1638 self.old_id = oid
1639 self.new_id = nid
1640
1641 if len(state) == 1:
1642 self.status = state
1643 self.level = None
1644 else:
1645 self.status = state[:1]
1646 self.level = state[1:]
1647 while self.level.startswith('0'):
1648 self.level = self.level[1:]
1649
1650 info = info[1:].split(' ')
1651 info =_Info(path, *info)
1652 if info.status in ('R', 'C'):
1653 info.src_path = info.path
1654 info.path = out.next()
1655 r[info.path] = info
1656 return r
1657 finally:
1658 p.Wait()
1659
1660 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001661 if self._bare:
1662 path = os.path.join(self._project.gitdir, HEAD)
1663 else:
1664 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001665 fd = open(path, 'rb')
1666 try:
1667 line = fd.read()
1668 finally:
1669 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001670 if line.startswith('ref: '):
1671 return line[5:-1]
1672 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001673
1674 def SetHead(self, ref, message=None):
1675 cmdv = []
1676 if message is not None:
1677 cmdv.extend(['-m', message])
1678 cmdv.append(HEAD)
1679 cmdv.append(ref)
1680 self.symbolic_ref(*cmdv)
1681
1682 def DetachHead(self, new, message=None):
1683 cmdv = ['--no-deref']
1684 if message is not None:
1685 cmdv.extend(['-m', message])
1686 cmdv.append(HEAD)
1687 cmdv.append(new)
1688 self.update_ref(*cmdv)
1689
1690 def UpdateRef(self, name, new, old=None,
1691 message=None,
1692 detach=False):
1693 cmdv = []
1694 if message is not None:
1695 cmdv.extend(['-m', message])
1696 if detach:
1697 cmdv.append('--no-deref')
1698 cmdv.append(name)
1699 cmdv.append(new)
1700 if old is not None:
1701 cmdv.append(old)
1702 self.update_ref(*cmdv)
1703
1704 def DeleteRef(self, name, old=None):
1705 if not old:
1706 old = self.rev_parse(name)
1707 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001708 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001709
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001710 def rev_list(self, *args, **kw):
1711 if 'format' in kw:
1712 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1713 else:
1714 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715 cmdv.extend(args)
1716 p = GitCommand(self._project,
1717 cmdv,
1718 bare = self._bare,
1719 capture_stdout = True,
1720 capture_stderr = True)
1721 r = []
1722 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001723 if line[-1] == '\n':
1724 line = line[:-1]
1725 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726 if p.Wait() != 0:
1727 raise GitError('%s rev-list %s: %s' % (
1728 self._project.name,
1729 str(args),
1730 p.stderr))
1731 return r
1732
1733 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001734 """Allow arbitrary git commands using pythonic syntax.
1735
1736 This allows you to do things like:
1737 git_obj.rev_parse('HEAD')
1738
1739 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1740 run. We'll replace the '_' with a '-' and try to run a git command.
1741 Any other arguments will be passed to the git command.
1742
1743 Args:
1744 name: The name of the git command to call. Any '_' characters will
1745 be replaced with '-'.
1746
1747 Returns:
1748 A callable object that will try to call git with the named command.
1749 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750 name = name.replace('_', '-')
1751 def runner(*args):
1752 cmdv = [name]
1753 cmdv.extend(args)
1754 p = GitCommand(self._project,
1755 cmdv,
1756 bare = self._bare,
1757 capture_stdout = True,
1758 capture_stderr = True)
1759 if p.Wait() != 0:
1760 raise GitError('%s %s: %s' % (
1761 self._project.name,
1762 name,
1763 p.stderr))
1764 r = p.stdout
1765 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1766 return r[:-1]
1767 return r
1768 return runner
1769
1770
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001771class _PriorSyncFailedError(Exception):
1772 def __str__(self):
1773 return 'prior sync failed; rebase still in progress'
1774
1775class _DirtyError(Exception):
1776 def __str__(self):
1777 return 'contains uncommitted changes'
1778
1779class _InfoMessage(object):
1780 def __init__(self, project, text):
1781 self.project = project
1782 self.text = text
1783
1784 def Print(self, syncbuf):
1785 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1786 syncbuf.out.nl()
1787
1788class _Failure(object):
1789 def __init__(self, project, why):
1790 self.project = project
1791 self.why = why
1792
1793 def Print(self, syncbuf):
1794 syncbuf.out.fail('error: %s/: %s',
1795 self.project.relpath,
1796 str(self.why))
1797 syncbuf.out.nl()
1798
1799class _Later(object):
1800 def __init__(self, project, action):
1801 self.project = project
1802 self.action = action
1803
1804 def Run(self, syncbuf):
1805 out = syncbuf.out
1806 out.project('project %s/', self.project.relpath)
1807 out.nl()
1808 try:
1809 self.action()
1810 out.nl()
1811 return True
1812 except GitError, e:
1813 out.nl()
1814 return False
1815
1816class _SyncColoring(Coloring):
1817 def __init__(self, config):
1818 Coloring.__init__(self, config, 'reposync')
1819 self.project = self.printer('header', attr = 'bold')
1820 self.info = self.printer('info')
1821 self.fail = self.printer('fail', fg='red')
1822
1823class SyncBuffer(object):
1824 def __init__(self, config, detach_head=False):
1825 self._messages = []
1826 self._failures = []
1827 self._later_queue1 = []
1828 self._later_queue2 = []
1829
1830 self.out = _SyncColoring(config)
1831 self.out.redirect(sys.stderr)
1832
1833 self.detach_head = detach_head
1834 self.clean = True
1835
1836 def info(self, project, fmt, *args):
1837 self._messages.append(_InfoMessage(project, fmt % args))
1838
1839 def fail(self, project, err=None):
1840 self._failures.append(_Failure(project, err))
1841 self.clean = False
1842
1843 def later1(self, project, what):
1844 self._later_queue1.append(_Later(project, what))
1845
1846 def later2(self, project, what):
1847 self._later_queue2.append(_Later(project, what))
1848
1849 def Finish(self):
1850 self._PrintMessages()
1851 self._RunLater()
1852 self._PrintMessages()
1853 return self.clean
1854
1855 def _RunLater(self):
1856 for q in ['_later_queue1', '_later_queue2']:
1857 if not self._RunQueue(q):
1858 return
1859
1860 def _RunQueue(self, queue):
1861 for m in getattr(self, queue):
1862 if not m.Run(self):
1863 self.clean = False
1864 return False
1865 setattr(self, queue, [])
1866 return True
1867
1868 def _PrintMessages(self):
1869 for m in self._messages:
1870 m.Print(self)
1871 for m in self._failures:
1872 m.Print(self)
1873
1874 self._messages = []
1875 self._failures = []
1876
1877
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001878class MetaProject(Project):
1879 """A special project housed under .repo.
1880 """
1881 def __init__(self, manifest, name, gitdir, worktree):
1882 repodir = manifest.repodir
1883 Project.__init__(self,
1884 manifest = manifest,
1885 name = name,
1886 gitdir = gitdir,
1887 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001888 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001889 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001890 revisionExpr = 'refs/heads/master',
1891 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001892
1893 def PreSync(self):
1894 if self.Exists:
1895 cb = self.CurrentBranch
1896 if cb:
1897 base = self.GetBranch(cb).merge
1898 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001899 self.revisionExpr = base
1900 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001901
1902 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07001903 def LastFetch(self):
1904 try:
1905 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
1906 return os.path.getmtime(fh)
1907 except OSError:
1908 return 0
1909
1910 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001911 def HasChanges(self):
1912 """Has the remote received new commits not yet checked out?
1913 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001914 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001915 return False
1916
1917 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001918 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001919 head = self.work_git.GetHead()
1920 if head.startswith(R_HEADS):
1921 try:
1922 head = all[head]
1923 except KeyError:
1924 head = None
1925
1926 if revid == head:
1927 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001928 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001929 return True
1930 return False