blob: 3efc44522e68df1ee38d161b5d14957f46969974 [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
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700794 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 """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
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700810 if selected_branch and branch != selected_branch:
811 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800813 rb = self.GetUploadableBranch(branch)
814 if rb:
815 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816 return ready
817
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800818 def GetUploadableBranch(self, branch_name):
819 """Get a single uploadable branch, or None.
820 """
821 branch = self.GetBranch(branch_name)
822 base = branch.LocalMerge
823 if branch.LocalMerge:
824 rb = ReviewableBranch(self, branch, base)
825 if rb.commits:
826 return rb
827 return None
828
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700829 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700830 people=([],[]),
831 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 """Uploads the named branch for code review.
833 """
834 if branch is None:
835 branch = self.CurrentBranch
836 if branch is None:
837 raise GitError('not currently on a branch')
838
839 branch = self.GetBranch(branch)
840 if not branch.LocalMerge:
841 raise GitError('branch %s does not track a remote' % branch.name)
842 if not branch.remote.review:
843 raise GitError('remote %s has no review url' % branch.remote.name)
844
845 dest_branch = branch.merge
846 if not dest_branch.startswith(R_HEADS):
847 dest_branch = R_HEADS + dest_branch
848
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800849 if not branch.remote.projectname:
850 branch.remote.projectname = self.name
851 branch.remote.Save()
852
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800853 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800854 if dest_branch.startswith(R_HEADS):
855 dest_branch = dest_branch[len(R_HEADS):]
856
857 rp = ['gerrit receive-pack']
858 for e in people[0]:
859 rp.append('--reviewer=%s' % sq(e))
860 for e in people[1]:
861 rp.append('--cc=%s' % sq(e))
862
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700863 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
864 if auto_topic:
865 ref_spec = ref_spec + '/' + branch.name
866
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800867 cmd = ['push']
868 cmd.append('--receive-pack=%s' % " ".join(rp))
869 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700870 cmd.append(ref_spec)
871
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800872 if GitCommand(self, cmd, bare = True).Wait() != 0:
873 raise UploadError('Upload failed')
874
875 else:
876 raise UploadError('Unsupported protocol %s' \
877 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878
879 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
880 self.bare_git.UpdateRef(R_PUB + branch.name,
881 R_HEADS + branch.name,
882 message = msg)
883
884
885## Sync ##
886
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700887 def Sync_NetworkHalf(self, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 """Perform only the network IO portion of the sync process.
889 Local working directory/branch state is not affected.
890 """
Shawn O. Pearce88443382010-10-08 10:02:09 +0200891 is_new = not self.Exists
892 if is_new:
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700893 if not quiet:
894 print >>sys.stderr
895 print >>sys.stderr, 'Initializing project %s ...' % self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800897
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 self._InitRemote()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700899 if not self._RemoteFetch(initial=is_new, quiet=quiet):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800901
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200902 #Check that the requested ref was found after fetch
903 #
904 try:
905 self.GetRevisionId()
906 except ManifestInvalidRevisionError:
907 # if the ref is a tag. We can try fetching
908 # the tag manually as a last resort
909 #
910 rev = self.revisionExpr
911 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700912 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200913
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800914 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800915 self._InitMRef()
916 else:
917 self._InitMirrorHead()
918 try:
919 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
920 except OSError:
921 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800923
924 def PostRepoUpgrade(self):
925 self._InitHooks()
926
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 def _CopyFiles(self):
928 for file in self.copyfiles:
929 file._Copy()
930
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700931 def GetRevisionId(self, all=None):
932 if self.revisionId:
933 return self.revisionId
934
935 rem = self.GetRemote(self.remote.name)
936 rev = rem.ToLocal(self.revisionExpr)
937
938 if all is not None and rev in all:
939 return all[rev]
940
941 try:
942 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
943 except GitError:
944 raise ManifestInvalidRevisionError(
945 'revision %s in %s not found' % (self.revisionExpr,
946 self.name))
947
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700948 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 """Perform only the local IO portion of the sync process.
950 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700952 all = self.bare_ref.all
953 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700954 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800955
956 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700957 head = self.work_git.GetHead()
958 if head.startswith(R_HEADS):
959 branch = head[len(R_HEADS):]
960 try:
961 head = all[head]
962 except KeyError:
963 head = None
964 else:
965 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700967 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 # Currently on a detached HEAD. The user is assumed to
969 # not have any local modifications worth worrying about.
970 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700971 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700972 syncbuf.fail(self, _PriorSyncFailedError())
973 return
974
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700975 if head == revid:
976 # No changes; don't do anything further.
977 #
978 return
979
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700980 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700982 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700984 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700985 except GitError, e:
986 syncbuf.fail(self, e)
987 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700989 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700991 if head == revid:
992 # No changes; don't do anything further.
993 #
994 return
995
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700998 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 # The current branch has no tracking configuration.
1000 # Jump off it to a deatched HEAD.
1001 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001002 syncbuf.info(self,
1003 "leaving %s; does not track upstream",
1004 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001006 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001007 except GitError, e:
1008 syncbuf.fail(self, e)
1009 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001011 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001013 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001014 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001016 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 if not_merged:
1018 if upstream_gain:
1019 # The user has published this branch and some of those
1020 # commits are not yet merged upstream. We do not want
1021 # to rewrite the published commits so we punt.
1022 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001023 syncbuf.fail(self,
1024 "branch %s is published (but not merged) and is now %d commits behind"
1025 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001026 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001027 elif pub == head:
1028 # All published commits are merged, and thus we are a
1029 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001030 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001031 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001032 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001033 self._CopyFiles()
1034 syncbuf.later1(self, _doff)
1035 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001037 # Examine the local commits not in the remote. Find the
1038 # last one attributed to this user, if any.
1039 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001041 last_mine = None
1042 cnt_mine = 0
1043 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001044 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001045 if committer_email == self.UserEmail:
1046 last_mine = commit_id
1047 cnt_mine += 1
1048
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001049 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001050 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051
1052 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 syncbuf.fail(self, _DirtyError())
1054 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001056 # If the upstream switched on us, warn the user.
1057 #
1058 if branch.merge != self.revisionExpr:
1059 if branch.merge and self.revisionExpr:
1060 syncbuf.info(self,
1061 'manifest switched %s...%s',
1062 branch.merge,
1063 self.revisionExpr)
1064 elif branch.merge:
1065 syncbuf.info(self,
1066 'manifest no longer tracks %s',
1067 branch.merge)
1068
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001069 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001071 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001073 syncbuf.info(self,
1074 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001075 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001077 branch.remote = self.GetRemote(self.remote.name)
1078 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 branch.Save()
1080
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001081 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 self._CopyFiles()
1085 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001086 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001088 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001089 self._CopyFiles()
1090 except GitError, e:
1091 syncbuf.fail(self, e)
1092 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001094 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001095 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001096 self._CopyFiles()
1097 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001099 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 # dest should already be an absolute path, but src is project relative
1101 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001102 abssrc = os.path.join(self.worktree, src)
1103 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001105 def DownloadPatchSet(self, change_id, patch_id):
1106 """Download a single patch set of a single change to FETCH_HEAD.
1107 """
1108 remote = self.GetRemote(self.remote.name)
1109
1110 cmd = ['fetch', remote.name]
1111 cmd.append('refs/changes/%2.2d/%d/%d' \
1112 % (change_id % 100, change_id, patch_id))
1113 cmd.extend(map(lambda x: str(x), remote.fetch))
1114 if GitCommand(self, cmd, bare=True).Wait() != 0:
1115 return None
1116 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001117 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001118 change_id,
1119 patch_id,
1120 self.bare_git.rev_parse('FETCH_HEAD'))
1121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122
1123## Branch Management ##
1124
1125 def StartBranch(self, name):
1126 """Create a new branch off the manifest's revision.
1127 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001128 head = self.work_git.GetHead()
1129 if head == (R_HEADS + name):
1130 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001132 all = self.bare_ref.all
1133 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001134 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001135 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001136 capture_stdout = True,
1137 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001138
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001139 branch = self.GetBranch(name)
1140 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001141 branch.merge = self.revisionExpr
1142 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001143
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001144 if head.startswith(R_HEADS):
1145 try:
1146 head = all[head]
1147 except KeyError:
1148 head = None
1149
1150 if revid and head and revid == head:
1151 ref = os.path.join(self.gitdir, R_HEADS + name)
1152 try:
1153 os.makedirs(os.path.dirname(ref))
1154 except OSError:
1155 pass
1156 _lwrite(ref, '%s\n' % revid)
1157 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1158 'ref: %s%s\n' % (R_HEADS, name))
1159 branch.Save()
1160 return True
1161
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001162 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001164 capture_stdout = True,
1165 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001166 branch.Save()
1167 return True
1168 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169
Wink Saville02d79452009-04-10 13:01:24 -07001170 def CheckoutBranch(self, name):
1171 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001172
1173 Args:
1174 name: The name of the branch to checkout.
1175
1176 Returns:
1177 True if the checkout succeeded; False if it didn't; None if the branch
1178 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001179 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001180 rev = R_HEADS + name
1181 head = self.work_git.GetHead()
1182 if head == rev:
1183 # Already on the branch
1184 #
1185 return True
Wink Saville02d79452009-04-10 13:01:24 -07001186
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001187 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001188 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001189 revid = all[rev]
1190 except KeyError:
1191 # Branch does not exist in this project
1192 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001193 return None
Wink Saville02d79452009-04-10 13:01:24 -07001194
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001195 if head.startswith(R_HEADS):
1196 try:
1197 head = all[head]
1198 except KeyError:
1199 head = None
1200
1201 if head == revid:
1202 # Same revision; just update HEAD to point to the new
1203 # target branch, but otherwise take no other action.
1204 #
1205 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1206 'ref: %s%s\n' % (R_HEADS, name))
1207 return True
1208
1209 return GitCommand(self,
1210 ['checkout', name, '--'],
1211 capture_stdout = True,
1212 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001213
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001214 def AbandonBranch(self, name):
1215 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001216
1217 Args:
1218 name: The name of the branch to abandon.
1219
1220 Returns:
1221 True if the abandon succeeded; False if it didn't; None if the branch
1222 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001223 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001224 rev = R_HEADS + name
1225 all = self.bare_ref.all
1226 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001227 # Doesn't exist
1228 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001229
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001230 head = self.work_git.GetHead()
1231 if head == rev:
1232 # We can't destroy the branch while we are sitting
1233 # on it. Switch to a detached HEAD.
1234 #
1235 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001236
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001237 revid = self.GetRevisionId(all)
1238 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001239 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1240 '%s\n' % revid)
1241 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001242 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001243
1244 return GitCommand(self,
1245 ['branch', '-D', name],
1246 capture_stdout = True,
1247 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001248
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249 def PruneHeads(self):
1250 """Prune any topic branches already merged into upstream.
1251 """
1252 cb = self.CurrentBranch
1253 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001254 left = self._allrefs
1255 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 if name.startswith(R_HEADS):
1257 name = name[len(R_HEADS):]
1258 if cb is None or name != cb:
1259 kill.append(name)
1260
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001261 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 if cb is not None \
1263 and not self._revlist(HEAD + '...' + rev) \
1264 and not self.IsDirty(consider_untracked = False):
1265 self.work_git.DetachHead(HEAD)
1266 kill.append(cb)
1267
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001269 old = self.bare_git.GetHead()
1270 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1272
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273 try:
1274 self.bare_git.DetachHead(rev)
1275
1276 b = ['branch', '-d']
1277 b.extend(kill)
1278 b = GitCommand(self, b, bare=True,
1279 capture_stdout=True,
1280 capture_stderr=True)
1281 b.Wait()
1282 finally:
1283 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001284 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001286 for branch in kill:
1287 if (R_HEADS + branch) not in left:
1288 self.CleanPublishedCache()
1289 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290
1291 if cb and cb not in kill:
1292 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001293 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294
1295 kept = []
1296 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001297 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 branch = self.GetBranch(branch)
1299 base = branch.LocalMerge
1300 if not base:
1301 base = rev
1302 kept.append(ReviewableBranch(self, branch, base))
1303 return kept
1304
1305
1306## Direct Git Commands ##
1307
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001308 def _RemoteFetch(self, name=None, tag=None,
1309 initial=False,
1310 quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 if not name:
1312 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001313
1314 ssh_proxy = False
1315 if self.GetRemote(name).PreConnectFetch():
1316 ssh_proxy = True
1317
Shawn O. Pearce88443382010-10-08 10:02:09 +02001318 if initial:
1319 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1320 try:
1321 fd = open(alt, 'rb')
1322 try:
1323 ref_dir = fd.readline()
1324 if ref_dir and ref_dir.endswith('\n'):
1325 ref_dir = ref_dir[:-1]
1326 finally:
1327 fd.close()
1328 except IOError, e:
1329 ref_dir = None
1330
1331 if ref_dir and 'objects' == os.path.basename(ref_dir):
1332 ref_dir = os.path.dirname(ref_dir)
1333 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1334 remote = self.GetRemote(name)
1335
1336 all = self.bare_ref.all
1337 ids = set(all.values())
1338 tmp = set()
1339
1340 for r, id in GitRefs(ref_dir).all.iteritems():
1341 if r not in all:
1342 if r.startswith(R_TAGS) or remote.WritesTo(r):
1343 all[r] = id
1344 ids.add(id)
1345 continue
1346
1347 if id in ids:
1348 continue
1349
1350 r = 'refs/_alt/%s' % id
1351 all[r] = id
1352 ids.add(id)
1353 tmp.add(r)
1354
1355 ref_names = list(all.keys())
1356 ref_names.sort()
1357
1358 tmp_packed = ''
1359 old_packed = ''
1360
1361 for r in ref_names:
1362 line = '%s %s\n' % (all[r], r)
1363 tmp_packed += line
1364 if r not in tmp:
1365 old_packed += line
1366
1367 _lwrite(packed_refs, tmp_packed)
1368
1369 else:
1370 ref_dir = None
1371
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001372 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001373
1374 # The --depth option only affects the initial fetch; after that we'll do
1375 # full fetches of changes.
1376 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1377 if depth and initial:
1378 cmd.append('--depth=%s' % depth)
1379
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001380 if quiet:
1381 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001382 if not self.worktree:
1383 cmd.append('--update-head-ok')
1384 cmd.append(name)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001385 if tag is not None:
1386 cmd.append('tag')
1387 cmd.append(tag)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001388
1389 ok = GitCommand(self,
1390 cmd,
1391 bare = True,
1392 ssh_proxy = ssh_proxy).Wait() == 0
1393
1394 if initial:
1395 if ref_dir:
1396 if old_packed != '':
1397 _lwrite(packed_refs, old_packed)
1398 else:
1399 os.remove(packed_refs)
1400 self.bare_git.pack_refs('--all', '--prune')
1401
1402 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
1404 def _Checkout(self, rev, quiet=False):
1405 cmd = ['checkout']
1406 if quiet:
1407 cmd.append('-q')
1408 cmd.append(rev)
1409 cmd.append('--')
1410 if GitCommand(self, cmd).Wait() != 0:
1411 if self._allrefs:
1412 raise GitError('%s checkout %s ' % (self.name, rev))
1413
1414 def _ResetHard(self, rev, quiet=True):
1415 cmd = ['reset', '--hard']
1416 if quiet:
1417 cmd.append('-q')
1418 cmd.append(rev)
1419 if GitCommand(self, cmd).Wait() != 0:
1420 raise GitError('%s reset --hard %s ' % (self.name, rev))
1421
1422 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001423 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424 if onto is not None:
1425 cmd.extend(['--onto', onto])
1426 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001427 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428 raise GitError('%s rebase %s ' % (self.name, upstream))
1429
1430 def _FastForward(self, head):
1431 cmd = ['merge', head]
1432 if GitCommand(self, cmd).Wait() != 0:
1433 raise GitError('%s merge %s ' % (self.name, head))
1434
1435 def _InitGitDir(self):
1436 if not os.path.exists(self.gitdir):
1437 os.makedirs(self.gitdir)
1438 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001439
Shawn O. Pearce88443382010-10-08 10:02:09 +02001440 mp = self.manifest.manifestProject
1441 ref_dir = mp.config.GetString('repo.reference')
1442
1443 if ref_dir:
1444 mirror_git = os.path.join(ref_dir, self.name + '.git')
1445 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1446 self.relpath + '.git')
1447
1448 if os.path.exists(mirror_git):
1449 ref_dir = mirror_git
1450
1451 elif os.path.exists(repo_git):
1452 ref_dir = repo_git
1453
1454 else:
1455 ref_dir = None
1456
1457 if ref_dir:
1458 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1459 os.path.join(ref_dir, 'objects') + '\n')
1460
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001461 if self.manifest.IsMirror:
1462 self.config.SetString('core.bare', 'true')
1463 else:
1464 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465
1466 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001467 try:
1468 to_rm = os.listdir(hooks)
1469 except OSError:
1470 to_rm = []
1471 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001473 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474
1475 m = self.manifest.manifestProject.config
1476 for key in ['user.name', 'user.email']:
1477 if m.Has(key, include_defaults = False):
1478 self.config.SetString(key, m.GetString(key))
1479
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001480 def _InitHooks(self):
1481 hooks = self._gitdir_path('hooks')
1482 if not os.path.exists(hooks):
1483 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001484 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001485 name = os.path.basename(stock_hook)
1486
Victor Boivie65e0f352011-04-18 11:23:29 +02001487 if name in ('commit-msg',) and not self.remote.review \
1488 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001489 # Don't install a Gerrit Code Review hook if this
1490 # project does not appear to use it for reviews.
1491 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001492 # Since the manifest project is one of those, but also
1493 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001494 continue
1495
1496 dst = os.path.join(hooks, name)
1497 if os.path.islink(dst):
1498 continue
1499 if os.path.exists(dst):
1500 if filecmp.cmp(stock_hook, dst, shallow=False):
1501 os.remove(dst)
1502 else:
1503 _error("%s: Not replacing %s hook", self.relpath, name)
1504 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001505 try:
1506 os.symlink(relpath(stock_hook, dst), dst)
1507 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001508 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001509 raise GitError('filesystem must support symlinks')
1510 else:
1511 raise
1512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001513 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001514 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001516 remote.url = self.remote.url
1517 remote.review = self.remote.review
1518 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001519
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001520 if self.worktree:
1521 remote.ResetFetch(mirror=False)
1522 else:
1523 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524 remote.Save()
1525
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526 def _InitMRef(self):
1527 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001528 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001529
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001530 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001531 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001532
1533 def _InitAnyMRef(self, ref):
1534 cur = self.bare_ref.symref(ref)
1535
1536 if self.revisionId:
1537 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1538 msg = 'manifest set to %s' % self.revisionId
1539 dst = self.revisionId + '^0'
1540 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1541 else:
1542 remote = self.GetRemote(self.remote.name)
1543 dst = remote.ToLocal(self.revisionExpr)
1544 if cur != dst:
1545 msg = 'manifest set to %s' % self.revisionExpr
1546 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548 def _InitWorkTree(self):
1549 dotgit = os.path.join(self.worktree, '.git')
1550 if not os.path.exists(dotgit):
1551 os.makedirs(dotgit)
1552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553 for name in ['config',
1554 'description',
1555 'hooks',
1556 'info',
1557 'logs',
1558 'objects',
1559 'packed-refs',
1560 'refs',
1561 'rr-cache',
1562 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001563 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001564 src = os.path.join(self.gitdir, name)
1565 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001566 if os.path.islink(dst) or not os.path.exists(dst):
1567 os.symlink(relpath(src, dst), dst)
1568 else:
1569 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001570 except OSError, e:
1571 if e.errno == errno.EPERM:
1572 raise GitError('filesystem must support symlinks')
1573 else:
1574 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001575
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001576 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577
1578 cmd = ['read-tree', '--reset', '-u']
1579 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001580 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001581 if GitCommand(self, cmd).Wait() != 0:
1582 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001583
1584 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1585 if not os.path.exists(rr_cache):
1586 os.makedirs(rr_cache)
1587
Shawn O. Pearce93609662009-04-21 10:50:33 -07001588 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589
1590 def _gitdir_path(self, path):
1591 return os.path.join(self.gitdir, path)
1592
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001593 def _revlist(self, *args, **kw):
1594 a = []
1595 a.extend(args)
1596 a.append('--')
1597 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598
1599 @property
1600 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001601 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602
1603 class _GitGetByExec(object):
1604 def __init__(self, project, bare):
1605 self._project = project
1606 self._bare = bare
1607
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001608 def LsOthers(self):
1609 p = GitCommand(self._project,
1610 ['ls-files',
1611 '-z',
1612 '--others',
1613 '--exclude-standard'],
1614 bare = False,
1615 capture_stdout = True,
1616 capture_stderr = True)
1617 if p.Wait() == 0:
1618 out = p.stdout
1619 if out:
1620 return out[:-1].split("\0")
1621 return []
1622
1623 def DiffZ(self, name, *args):
1624 cmd = [name]
1625 cmd.append('-z')
1626 cmd.extend(args)
1627 p = GitCommand(self._project,
1628 cmd,
1629 bare = False,
1630 capture_stdout = True,
1631 capture_stderr = True)
1632 try:
1633 out = p.process.stdout.read()
1634 r = {}
1635 if out:
1636 out = iter(out[:-1].split('\0'))
1637 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001638 try:
1639 info = out.next()
1640 path = out.next()
1641 except StopIteration:
1642 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643
1644 class _Info(object):
1645 def __init__(self, path, omode, nmode, oid, nid, state):
1646 self.path = path
1647 self.src_path = None
1648 self.old_mode = omode
1649 self.new_mode = nmode
1650 self.old_id = oid
1651 self.new_id = nid
1652
1653 if len(state) == 1:
1654 self.status = state
1655 self.level = None
1656 else:
1657 self.status = state[:1]
1658 self.level = state[1:]
1659 while self.level.startswith('0'):
1660 self.level = self.level[1:]
1661
1662 info = info[1:].split(' ')
1663 info =_Info(path, *info)
1664 if info.status in ('R', 'C'):
1665 info.src_path = info.path
1666 info.path = out.next()
1667 r[info.path] = info
1668 return r
1669 finally:
1670 p.Wait()
1671
1672 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001673 if self._bare:
1674 path = os.path.join(self._project.gitdir, HEAD)
1675 else:
1676 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001677 fd = open(path, 'rb')
1678 try:
1679 line = fd.read()
1680 finally:
1681 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001682 if line.startswith('ref: '):
1683 return line[5:-1]
1684 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685
1686 def SetHead(self, ref, message=None):
1687 cmdv = []
1688 if message is not None:
1689 cmdv.extend(['-m', message])
1690 cmdv.append(HEAD)
1691 cmdv.append(ref)
1692 self.symbolic_ref(*cmdv)
1693
1694 def DetachHead(self, new, message=None):
1695 cmdv = ['--no-deref']
1696 if message is not None:
1697 cmdv.extend(['-m', message])
1698 cmdv.append(HEAD)
1699 cmdv.append(new)
1700 self.update_ref(*cmdv)
1701
1702 def UpdateRef(self, name, new, old=None,
1703 message=None,
1704 detach=False):
1705 cmdv = []
1706 if message is not None:
1707 cmdv.extend(['-m', message])
1708 if detach:
1709 cmdv.append('--no-deref')
1710 cmdv.append(name)
1711 cmdv.append(new)
1712 if old is not None:
1713 cmdv.append(old)
1714 self.update_ref(*cmdv)
1715
1716 def DeleteRef(self, name, old=None):
1717 if not old:
1718 old = self.rev_parse(name)
1719 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001720 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001721
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001722 def rev_list(self, *args, **kw):
1723 if 'format' in kw:
1724 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1725 else:
1726 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727 cmdv.extend(args)
1728 p = GitCommand(self._project,
1729 cmdv,
1730 bare = self._bare,
1731 capture_stdout = True,
1732 capture_stderr = True)
1733 r = []
1734 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001735 if line[-1] == '\n':
1736 line = line[:-1]
1737 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 if p.Wait() != 0:
1739 raise GitError('%s rev-list %s: %s' % (
1740 self._project.name,
1741 str(args),
1742 p.stderr))
1743 return r
1744
1745 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001746 """Allow arbitrary git commands using pythonic syntax.
1747
1748 This allows you to do things like:
1749 git_obj.rev_parse('HEAD')
1750
1751 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1752 run. We'll replace the '_' with a '-' and try to run a git command.
1753 Any other arguments will be passed to the git command.
1754
1755 Args:
1756 name: The name of the git command to call. Any '_' characters will
1757 be replaced with '-'.
1758
1759 Returns:
1760 A callable object that will try to call git with the named command.
1761 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762 name = name.replace('_', '-')
1763 def runner(*args):
1764 cmdv = [name]
1765 cmdv.extend(args)
1766 p = GitCommand(self._project,
1767 cmdv,
1768 bare = self._bare,
1769 capture_stdout = True,
1770 capture_stderr = True)
1771 if p.Wait() != 0:
1772 raise GitError('%s %s: %s' % (
1773 self._project.name,
1774 name,
1775 p.stderr))
1776 r = p.stdout
1777 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1778 return r[:-1]
1779 return r
1780 return runner
1781
1782
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001783class _PriorSyncFailedError(Exception):
1784 def __str__(self):
1785 return 'prior sync failed; rebase still in progress'
1786
1787class _DirtyError(Exception):
1788 def __str__(self):
1789 return 'contains uncommitted changes'
1790
1791class _InfoMessage(object):
1792 def __init__(self, project, text):
1793 self.project = project
1794 self.text = text
1795
1796 def Print(self, syncbuf):
1797 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1798 syncbuf.out.nl()
1799
1800class _Failure(object):
1801 def __init__(self, project, why):
1802 self.project = project
1803 self.why = why
1804
1805 def Print(self, syncbuf):
1806 syncbuf.out.fail('error: %s/: %s',
1807 self.project.relpath,
1808 str(self.why))
1809 syncbuf.out.nl()
1810
1811class _Later(object):
1812 def __init__(self, project, action):
1813 self.project = project
1814 self.action = action
1815
1816 def Run(self, syncbuf):
1817 out = syncbuf.out
1818 out.project('project %s/', self.project.relpath)
1819 out.nl()
1820 try:
1821 self.action()
1822 out.nl()
1823 return True
1824 except GitError, e:
1825 out.nl()
1826 return False
1827
1828class _SyncColoring(Coloring):
1829 def __init__(self, config):
1830 Coloring.__init__(self, config, 'reposync')
1831 self.project = self.printer('header', attr = 'bold')
1832 self.info = self.printer('info')
1833 self.fail = self.printer('fail', fg='red')
1834
1835class SyncBuffer(object):
1836 def __init__(self, config, detach_head=False):
1837 self._messages = []
1838 self._failures = []
1839 self._later_queue1 = []
1840 self._later_queue2 = []
1841
1842 self.out = _SyncColoring(config)
1843 self.out.redirect(sys.stderr)
1844
1845 self.detach_head = detach_head
1846 self.clean = True
1847
1848 def info(self, project, fmt, *args):
1849 self._messages.append(_InfoMessage(project, fmt % args))
1850
1851 def fail(self, project, err=None):
1852 self._failures.append(_Failure(project, err))
1853 self.clean = False
1854
1855 def later1(self, project, what):
1856 self._later_queue1.append(_Later(project, what))
1857
1858 def later2(self, project, what):
1859 self._later_queue2.append(_Later(project, what))
1860
1861 def Finish(self):
1862 self._PrintMessages()
1863 self._RunLater()
1864 self._PrintMessages()
1865 return self.clean
1866
1867 def _RunLater(self):
1868 for q in ['_later_queue1', '_later_queue2']:
1869 if not self._RunQueue(q):
1870 return
1871
1872 def _RunQueue(self, queue):
1873 for m in getattr(self, queue):
1874 if not m.Run(self):
1875 self.clean = False
1876 return False
1877 setattr(self, queue, [])
1878 return True
1879
1880 def _PrintMessages(self):
1881 for m in self._messages:
1882 m.Print(self)
1883 for m in self._failures:
1884 m.Print(self)
1885
1886 self._messages = []
1887 self._failures = []
1888
1889
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890class MetaProject(Project):
1891 """A special project housed under .repo.
1892 """
1893 def __init__(self, manifest, name, gitdir, worktree):
1894 repodir = manifest.repodir
1895 Project.__init__(self,
1896 manifest = manifest,
1897 name = name,
1898 gitdir = gitdir,
1899 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001900 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001901 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001902 revisionExpr = 'refs/heads/master',
1903 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001904
1905 def PreSync(self):
1906 if self.Exists:
1907 cb = self.CurrentBranch
1908 if cb:
1909 base = self.GetBranch(cb).merge
1910 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001911 self.revisionExpr = base
1912 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001913
1914 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07001915 def LastFetch(self):
1916 try:
1917 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
1918 return os.path.getmtime(fh)
1919 except OSError:
1920 return 0
1921
1922 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001923 def HasChanges(self):
1924 """Has the remote received new commits not yet checked out?
1925 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001926 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001927 return False
1928
1929 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001930 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001931 head = self.work_git.GetHead()
1932 if head.startswith(R_HEADS):
1933 try:
1934 head = all[head]
1935 except KeyError:
1936 head = None
1937
1938 if revid == head:
1939 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001940 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001941 return True
1942 return False