blob: 43f4713cf44a20b90e739e4b744143387705b322 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Anderson37282b42011-03-04 11:54:18 -080015import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
18import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
23import sys
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070024import time
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import urllib2
26
27from color import Coloring
28from git_command import GitCommand
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070029from git_config import GitConfig, IsId, GetSchemeFromUrl
30from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080031from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080032from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070033from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Shawn O. Pearced237b692009-04-17 18:49:50 -070035from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070037def _lwrite(path, content):
38 lock = '%s.lock' % path
39
40 fd = open(lock, 'wb')
41 try:
42 fd.write(content)
43 finally:
44 fd.close()
45
46 try:
47 os.rename(lock, path)
48 except OSError:
49 os.remove(lock)
50 raise
51
Shawn O. Pearce48244782009-04-16 08:25:57 -070052def _error(fmt, *args):
53 msg = fmt % args
54 print >>sys.stderr, 'error: %s' % msg
55
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056def not_rev(r):
57 return '^' + r
58
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080059def sq(r):
60 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080061
Doug Anderson8ced8642011-01-10 14:16:30 -080062_project_hook_list = None
63def _ProjectHooks():
64 """List the hooks present in the 'hooks' directory.
65
66 These hooks are project hooks and are copied to the '.git/hooks' directory
67 of all subprojects.
68
69 This function caches the list of hooks (based on the contents of the
70 'repo/hooks' directory) on the first call.
71
72 Returns:
73 A list of absolute paths to all of the files in the hooks directory.
74 """
75 global _project_hook_list
76 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080077 d = os.path.abspath(os.path.dirname(__file__))
78 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080079 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
80 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
82def relpath(dst, src):
83 src = os.path.dirname(src)
84 top = os.path.commonprefix([dst, src])
85 if top.endswith('/'):
86 top = top[:-1]
87 else:
88 top = os.path.dirname(top)
89
90 tmp = src
91 rel = ''
92 while top != tmp:
93 rel += '../'
94 tmp = os.path.dirname(tmp)
95 return rel + dst[len(top) + 1:]
96
97
Shawn O. Pearce632768b2008-10-23 11:58:52 -070098class DownloadedChange(object):
99 _commit_cache = None
100
101 def __init__(self, project, base, change_id, ps_id, commit):
102 self.project = project
103 self.base = base
104 self.change_id = change_id
105 self.ps_id = ps_id
106 self.commit = commit
107
108 @property
109 def commits(self):
110 if self._commit_cache is None:
111 self._commit_cache = self.project.bare_git.rev_list(
112 '--abbrev=8',
113 '--abbrev-commit',
114 '--pretty=oneline',
115 '--reverse',
116 '--date-order',
117 not_rev(self.base),
118 self.commit,
119 '--')
120 return self._commit_cache
121
122
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123class ReviewableBranch(object):
124 _commit_cache = None
125
126 def __init__(self, project, branch, base):
127 self.project = project
128 self.branch = branch
129 self.base = base
130
131 @property
132 def name(self):
133 return self.branch.name
134
135 @property
136 def commits(self):
137 if self._commit_cache is None:
138 self._commit_cache = self.project.bare_git.rev_list(
139 '--abbrev=8',
140 '--abbrev-commit',
141 '--pretty=oneline',
142 '--reverse',
143 '--date-order',
144 not_rev(self.base),
145 R_HEADS + self.name,
146 '--')
147 return self._commit_cache
148
149 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800150 def unabbrev_commits(self):
151 r = dict()
152 for commit in self.project.bare_git.rev_list(
153 not_rev(self.base),
154 R_HEADS + self.name,
155 '--'):
156 r[commit[0:8]] = commit
157 return r
158
159 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 def date(self):
161 return self.project.bare_git.log(
162 '--pretty=format:%cd',
163 '-n', '1',
164 R_HEADS + self.name,
165 '--')
166
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700167 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700169 people,
170 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700172 def GetPublishedRefs(self):
173 refs = {}
174 output = self.project.bare_git.ls_remote(
175 self.branch.remote.SshReviewUrl(self.project.UserEmail),
176 'refs/changes/*')
177 for line in output.split('\n'):
178 try:
179 (sha, ref) = line.split()
180 refs[sha] = ref
181 except ValueError:
182 pass
183
184 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185
186class StatusColoring(Coloring):
187 def __init__(self, config):
188 Coloring.__init__(self, config, 'status')
189 self.project = self.printer('header', attr = 'bold')
190 self.branch = self.printer('header', attr = 'bold')
191 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700192 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193
194 self.added = self.printer('added', fg = 'green')
195 self.changed = self.printer('changed', fg = 'red')
196 self.untracked = self.printer('untracked', fg = 'red')
197
198
199class DiffColoring(Coloring):
200 def __init__(self, config):
201 Coloring.__init__(self, config, 'diff')
202 self.project = self.printer('header', attr = 'bold')
203
204
205class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800206 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 self.src = src
208 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800209 self.abs_src = abssrc
210 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211
212 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800213 src = self.abs_src
214 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700215 # copy file if it does not exist or is out of date
216 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
217 try:
218 # remove existing file first, since it might be read-only
219 if os.path.exists(dest):
220 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400221 else:
222 dir = os.path.dirname(dest)
223 if not os.path.isdir(dir):
224 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225 shutil.copy(src, dest)
226 # make the file read-only
227 mode = os.stat(dest)[stat.ST_MODE]
228 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
229 os.chmod(dest, mode)
230 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700231 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700233class RemoteSpec(object):
234 def __init__(self,
235 name,
236 url = None,
237 review = None):
238 self.name = name
239 self.url = url
240 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
Doug Anderson37282b42011-03-04 11:54:18 -0800242class RepoHook(object):
243 """A RepoHook contains information about a script to run as a hook.
244
245 Hooks are used to run a python script before running an upload (for instance,
246 to run presubmit checks). Eventually, we may have hooks for other actions.
247
248 This shouldn't be confused with files in the 'repo/hooks' directory. Those
249 files are copied into each '.git/hooks' folder for each project. Repo-level
250 hooks are associated instead with repo actions.
251
252 Hooks are always python. When a hook is run, we will load the hook into the
253 interpreter and execute its main() function.
254 """
255 def __init__(self,
256 hook_type,
257 hooks_project,
258 topdir,
259 abort_if_user_denies=False):
260 """RepoHook constructor.
261
262 Params:
263 hook_type: A string representing the type of hook. This is also used
264 to figure out the name of the file containing the hook. For
265 example: 'pre-upload'.
266 hooks_project: The project containing the repo hooks. If you have a
267 manifest, this is manifest.repo_hooks_project. OK if this is None,
268 which will make the hook a no-op.
269 topdir: Repo's top directory (the one containing the .repo directory).
270 Scripts will run with CWD as this directory. If you have a manifest,
271 this is manifest.topdir
272 abort_if_user_denies: If True, we'll throw a HookError() if the user
273 doesn't allow us to run the hook.
274 """
275 self._hook_type = hook_type
276 self._hooks_project = hooks_project
277 self._topdir = topdir
278 self._abort_if_user_denies = abort_if_user_denies
279
280 # Store the full path to the script for convenience.
281 if self._hooks_project:
282 self._script_fullpath = os.path.join(self._hooks_project.worktree,
283 self._hook_type + '.py')
284 else:
285 self._script_fullpath = None
286
287 def _GetHash(self):
288 """Return a hash of the contents of the hooks directory.
289
290 We'll just use git to do this. This hash has the property that if anything
291 changes in the directory we will return a different has.
292
293 SECURITY CONSIDERATION:
294 This hash only represents the contents of files in the hook directory, not
295 any other files imported or called by hooks. Changes to imported files
296 can change the script behavior without affecting the hash.
297
298 Returns:
299 A string representing the hash. This will always be ASCII so that it can
300 be printed to the user easily.
301 """
302 assert self._hooks_project, "Must have hooks to calculate their hash."
303
304 # We will use the work_git object rather than just calling GetRevisionId().
305 # That gives us a hash of the latest checked in version of the files that
306 # the user will actually be executing. Specifically, GetRevisionId()
307 # doesn't appear to change even if a user checks out a different version
308 # of the hooks repo (via git checkout) nor if a user commits their own revs.
309 #
310 # NOTE: Local (non-committed) changes will not be factored into this hash.
311 # I think this is OK, since we're really only worried about warning the user
312 # about upstream changes.
313 return self._hooks_project.work_git.rev_parse('HEAD')
314
315 def _GetMustVerb(self):
316 """Return 'must' if the hook is required; 'should' if not."""
317 if self._abort_if_user_denies:
318 return 'must'
319 else:
320 return 'should'
321
322 def _CheckForHookApproval(self):
323 """Check to see whether this hook has been approved.
324
325 We'll look at the hash of all of the hooks. If this matches the hash that
326 the user last approved, we're done. If it doesn't, we'll ask the user
327 about approval.
328
329 Note that we ask permission for each individual hook even though we use
330 the hash of all hooks when detecting changes. We'd like the user to be
331 able to approve / deny each hook individually. We only use the hash of all
332 hooks because there is no other easy way to detect changes to local imports.
333
334 Returns:
335 True if this hook is approved to run; False otherwise.
336
337 Raises:
338 HookError: Raised if the user doesn't approve and abort_if_user_denies
339 was passed to the consturctor.
340 """
341 hooks_dir = self._hooks_project.worktree
342 hooks_config = self._hooks_project.config
343 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
344
345 # Get the last hash that the user approved for this hook; may be None.
346 old_hash = hooks_config.GetString(git_approval_key)
347
348 # Get the current hash so we can tell if scripts changed since approval.
349 new_hash = self._GetHash()
350
351 if old_hash is not None:
352 # User previously approved hook and asked not to be prompted again.
353 if new_hash == old_hash:
354 # Approval matched. We're done.
355 return True
356 else:
357 # Give the user a reason why we're prompting, since they last told
358 # us to "never ask again".
359 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
360 self._hook_type)
361 else:
362 prompt = ''
363
364 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
365 if sys.stdout.isatty():
366 prompt += ('Repo %s run the script:\n'
367 ' %s\n'
368 '\n'
369 'Do you want to allow this script to run '
370 '(yes/yes-never-ask-again/NO)? ') % (
371 self._GetMustVerb(), self._script_fullpath)
372 response = raw_input(prompt).lower()
373 print
374
375 # User is doing a one-time approval.
376 if response in ('y', 'yes'):
377 return True
378 elif response == 'yes-never-ask-again':
379 hooks_config.SetString(git_approval_key, new_hash)
380 return True
381
382 # For anything else, we'll assume no approval.
383 if self._abort_if_user_denies:
384 raise HookError('You must allow the %s hook or use --no-verify.' %
385 self._hook_type)
386
387 return False
388
389 def _ExecuteHook(self, **kwargs):
390 """Actually execute the given hook.
391
392 This will run the hook's 'main' function in our python interpreter.
393
394 Args:
395 kwargs: Keyword arguments to pass to the hook. These are often specific
396 to the hook type. For instance, pre-upload hooks will contain
397 a project_list.
398 """
399 # Keep sys.path and CWD stashed away so that we can always restore them
400 # upon function exit.
401 orig_path = os.getcwd()
402 orig_syspath = sys.path
403
404 try:
405 # Always run hooks with CWD as topdir.
406 os.chdir(self._topdir)
407
408 # Put the hook dir as the first item of sys.path so hooks can do
409 # relative imports. We want to replace the repo dir as [0] so
410 # hooks can't import repo files.
411 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
412
413 # Exec, storing global context in the context dict. We catch exceptions
414 # and convert to a HookError w/ just the failing traceback.
415 context = {}
416 try:
417 execfile(self._script_fullpath, context)
418 except Exception:
419 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
420 traceback.format_exc(), self._hook_type))
421
422 # Running the script should have defined a main() function.
423 if 'main' not in context:
424 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
425
426
427 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
428 # We don't actually want hooks to define their main with this argument--
429 # it's there to remind them that their hook should always take **kwargs.
430 # For instance, a pre-upload hook should be defined like:
431 # def main(project_list, **kwargs):
432 #
433 # This allows us to later expand the API without breaking old hooks.
434 kwargs = kwargs.copy()
435 kwargs['hook_should_take_kwargs'] = True
436
437 # Call the main function in the hook. If the hook should cause the
438 # build to fail, it will raise an Exception. We'll catch that convert
439 # to a HookError w/ just the failing traceback.
440 try:
441 context['main'](**kwargs)
442 except Exception:
443 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
444 'above.' % (
445 traceback.format_exc(), self._hook_type))
446 finally:
447 # Restore sys.path and CWD.
448 sys.path = orig_syspath
449 os.chdir(orig_path)
450
451 def Run(self, user_allows_all_hooks, **kwargs):
452 """Run the hook.
453
454 If the hook doesn't exist (because there is no hooks project or because
455 this particular hook is not enabled), this is a no-op.
456
457 Args:
458 user_allows_all_hooks: If True, we will never prompt about running the
459 hook--we'll just assume it's OK to run it.
460 kwargs: Keyword arguments to pass to the hook. These are often specific
461 to the hook type. For instance, pre-upload hooks will contain
462 a project_list.
463
464 Raises:
465 HookError: If there was a problem finding the hook or the user declined
466 to run a required hook (from _CheckForHookApproval).
467 """
468 # No-op if there is no hooks project or if hook is disabled.
469 if ((not self._hooks_project) or
470 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
471 return
472
473 # Bail with a nice error if we can't find the hook.
474 if not os.path.isfile(self._script_fullpath):
475 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
476
477 # Make sure the user is OK with running the hook.
478 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
479 return
480
481 # Run the hook with the same version of python we're using.
482 self._ExecuteHook(**kwargs)
483
484
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700485class Project(object):
486 def __init__(self,
487 manifest,
488 name,
489 remote,
490 gitdir,
491 worktree,
492 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700493 revisionExpr,
494 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700495 self.manifest = manifest
496 self.name = name
497 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800498 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800499 if worktree:
500 self.worktree = worktree.replace('\\', '/')
501 else:
502 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700503 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700504 self.revisionExpr = revisionExpr
505
506 if revisionId is None \
507 and revisionExpr \
508 and IsId(revisionExpr):
509 self.revisionId = revisionExpr
510 else:
511 self.revisionId = revisionId
512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.copyfiles = []
515 self.config = GitConfig.ForRepository(
516 gitdir = self.gitdir,
517 defaults = self.manifest.globalConfig)
518
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800519 if self.worktree:
520 self.work_git = self._GitGetByExec(self, bare=False)
521 else:
522 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700524 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525
Doug Anderson37282b42011-03-04 11:54:18 -0800526 # This will be filled in if a project is later identified to be the
527 # project containing repo hooks.
528 self.enabled_repo_hooks = []
529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 @property
531 def Exists(self):
532 return os.path.isdir(self.gitdir)
533
534 @property
535 def CurrentBranch(self):
536 """Obtain the name of the currently checked out branch.
537 The branch name omits the 'refs/heads/' prefix.
538 None is returned if the project is on a detached HEAD.
539 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700540 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 if b.startswith(R_HEADS):
542 return b[len(R_HEADS):]
543 return None
544
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700545 def IsRebaseInProgress(self):
546 w = self.worktree
547 g = os.path.join(w, '.git')
548 return os.path.exists(os.path.join(g, 'rebase-apply')) \
549 or os.path.exists(os.path.join(g, 'rebase-merge')) \
550 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 def IsDirty(self, consider_untracked=True):
553 """Is the working directory modified in some way?
554 """
555 self.work_git.update_index('-q',
556 '--unmerged',
557 '--ignore-missing',
558 '--refresh')
559 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
560 return True
561 if self.work_git.DiffZ('diff-files'):
562 return True
563 if consider_untracked and self.work_git.LsOthers():
564 return True
565 return False
566
567 _userident_name = None
568 _userident_email = None
569
570 @property
571 def UserName(self):
572 """Obtain the user's personal name.
573 """
574 if self._userident_name is None:
575 self._LoadUserIdentity()
576 return self._userident_name
577
578 @property
579 def UserEmail(self):
580 """Obtain the user's email address. This is very likely
581 to be their Gerrit login.
582 """
583 if self._userident_email is None:
584 self._LoadUserIdentity()
585 return self._userident_email
586
587 def _LoadUserIdentity(self):
588 u = self.bare_git.var('GIT_COMMITTER_IDENT')
589 m = re.compile("^(.*) <([^>]*)> ").match(u)
590 if m:
591 self._userident_name = m.group(1)
592 self._userident_email = m.group(2)
593 else:
594 self._userident_name = ''
595 self._userident_email = ''
596
597 def GetRemote(self, name):
598 """Get the configuration for a single remote.
599 """
600 return self.config.GetRemote(name)
601
602 def GetBranch(self, name):
603 """Get the configuration for a single branch.
604 """
605 return self.config.GetBranch(name)
606
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700607 def GetBranches(self):
608 """Get all existing local branches.
609 """
610 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700611 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700612 heads = {}
613 pubd = {}
614
615 for name, id in all.iteritems():
616 if name.startswith(R_HEADS):
617 name = name[len(R_HEADS):]
618 b = self.GetBranch(name)
619 b.current = name == current
620 b.published = None
621 b.revision = id
622 heads[name] = b
623
624 for name, id in all.iteritems():
625 if name.startswith(R_PUB):
626 name = name[len(R_PUB):]
627 b = heads.get(name)
628 if b:
629 b.published = id
630
631 return heads
632
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633
634## Status Display ##
635
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500636 def HasChanges(self):
637 """Returns true if there are uncommitted changes.
638 """
639 self.work_git.update_index('-q',
640 '--unmerged',
641 '--ignore-missing',
642 '--refresh')
643 if self.IsRebaseInProgress():
644 return True
645
646 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
647 return True
648
649 if self.work_git.DiffZ('diff-files'):
650 return True
651
652 if self.work_git.LsOthers():
653 return True
654
655 return False
656
Terence Haddock4655e812011-03-31 12:33:34 +0200657 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700658 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200659
660 Args:
661 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662 """
663 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200664 if output_redir == None:
665 output_redir = sys.stdout
666 print >>output_redir, ''
667 print >>output_redir, 'project %s/' % self.relpath
668 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669 return
670
671 self.work_git.update_index('-q',
672 '--unmerged',
673 '--ignore-missing',
674 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700675 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700676 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
677 df = self.work_git.DiffZ('diff-files')
678 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700679 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700680 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681
682 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200683 if not output_redir == None:
684 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685 out.project('project %-40s', self.relpath + '/')
686
687 branch = self.CurrentBranch
688 if branch is None:
689 out.nobranch('(*** NO BRANCH ***)')
690 else:
691 out.branch('branch %s', branch)
692 out.nl()
693
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700694 if rb:
695 out.important('prior sync failed; rebase still in progress')
696 out.nl()
697
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698 paths = list()
699 paths.extend(di.keys())
700 paths.extend(df.keys())
701 paths.extend(do)
702
703 paths = list(set(paths))
704 paths.sort()
705
706 for p in paths:
707 try: i = di[p]
708 except KeyError: i = None
709
710 try: f = df[p]
711 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200712
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713 if i: i_status = i.status.upper()
714 else: i_status = '-'
715
716 if f: f_status = f.status.lower()
717 else: f_status = '-'
718
719 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800720 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 i.src_path, p, i.level)
722 else:
723 line = ' %s%s\t%s' % (i_status, f_status, p)
724
725 if i and not f:
726 out.added('%s', line)
727 elif (i and f) or (not i and f):
728 out.changed('%s', line)
729 elif not i and not f:
730 out.untracked('%s', line)
731 else:
732 out.write('%s', line)
733 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200734
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700735 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
737 def PrintWorkTreeDiff(self):
738 """Prints the status of the repository to stdout.
739 """
740 out = DiffColoring(self.config)
741 cmd = ['diff']
742 if out.is_on:
743 cmd.append('--color')
744 cmd.append(HEAD)
745 cmd.append('--')
746 p = GitCommand(self,
747 cmd,
748 capture_stdout = True,
749 capture_stderr = True)
750 has_diff = False
751 for line in p.process.stdout:
752 if not has_diff:
753 out.nl()
754 out.project('project %s/' % self.relpath)
755 out.nl()
756 has_diff = True
757 print line[:-1]
758 p.Wait()
759
760
761## Publish / Upload ##
762
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700763 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 """Was the branch published (uploaded) for code review?
765 If so, returns the SHA-1 hash of the last published
766 state for the branch.
767 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700768 key = R_PUB + branch
769 if all is None:
770 try:
771 return self.bare_git.rev_parse(key)
772 except GitError:
773 return None
774 else:
775 try:
776 return all[key]
777 except KeyError:
778 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700780 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781 """Prunes any stale published refs.
782 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700783 if all is None:
784 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785 heads = set()
786 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700787 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 if name.startswith(R_HEADS):
789 heads.add(name)
790 elif name.startswith(R_PUB):
791 canrm[name] = id
792
793 for name, id in canrm.iteritems():
794 n = name[len(R_PUB):]
795 if R_HEADS + n not in heads:
796 self.bare_git.DeleteRef(name, id)
797
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700798 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 """List any branches which can be uploaded for review.
800 """
801 heads = {}
802 pubed = {}
803
804 for name, id in self._allrefs.iteritems():
805 if name.startswith(R_HEADS):
806 heads[name[len(R_HEADS):]] = id
807 elif name.startswith(R_PUB):
808 pubed[name[len(R_PUB):]] = id
809
810 ready = []
811 for branch, id in heads.iteritems():
812 if branch in pubed and pubed[branch] == id:
813 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700814 if selected_branch and branch != selected_branch:
815 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800817 rb = self.GetUploadableBranch(branch)
818 if rb:
819 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 return ready
821
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800822 def GetUploadableBranch(self, branch_name):
823 """Get a single uploadable branch, or None.
824 """
825 branch = self.GetBranch(branch_name)
826 base = branch.LocalMerge
827 if branch.LocalMerge:
828 rb = ReviewableBranch(self, branch, base)
829 if rb.commits:
830 return rb
831 return None
832
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700833 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700834 people=([],[]),
835 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 """Uploads the named branch for code review.
837 """
838 if branch is None:
839 branch = self.CurrentBranch
840 if branch is None:
841 raise GitError('not currently on a branch')
842
843 branch = self.GetBranch(branch)
844 if not branch.LocalMerge:
845 raise GitError('branch %s does not track a remote' % branch.name)
846 if not branch.remote.review:
847 raise GitError('remote %s has no review url' % branch.remote.name)
848
849 dest_branch = branch.merge
850 if not dest_branch.startswith(R_HEADS):
851 dest_branch = R_HEADS + dest_branch
852
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800853 if not branch.remote.projectname:
854 branch.remote.projectname = self.name
855 branch.remote.Save()
856
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800857 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800858 if dest_branch.startswith(R_HEADS):
859 dest_branch = dest_branch[len(R_HEADS):]
860
861 rp = ['gerrit receive-pack']
862 for e in people[0]:
863 rp.append('--reviewer=%s' % sq(e))
864 for e in people[1]:
865 rp.append('--cc=%s' % sq(e))
866
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700867 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
868 if auto_topic:
869 ref_spec = ref_spec + '/' + branch.name
870
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800871 cmd = ['push']
872 cmd.append('--receive-pack=%s' % " ".join(rp))
873 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700874 cmd.append(ref_spec)
875
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800876 if GitCommand(self, cmd, bare = True).Wait() != 0:
877 raise UploadError('Upload failed')
878
879 else:
880 raise UploadError('Unsupported protocol %s' \
881 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882
883 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
884 self.bare_git.UpdateRef(R_PUB + branch.name,
885 R_HEADS + branch.name,
886 message = msg)
887
888
889## Sync ##
890
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700891 def Sync_NetworkHalf(self, quiet=False, is_new=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 """Perform only the network IO portion of the sync process.
893 Local working directory/branch state is not affected.
894 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700895 if is_new is None:
896 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200897 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 self._InitGitDir()
899 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700900
901 if is_new:
902 alt = os.path.join(self.gitdir, 'objects/info/alternates')
903 try:
904 fd = open(alt, 'rb')
905 try:
906 alt_dir = fd.readline().rstrip()
907 finally:
908 fd.close()
909 except IOError:
910 alt_dir = None
911 else:
912 alt_dir = None
913
914 if alt_dir is None and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
915 is_new = False
916
917 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800919
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200920 #Check that the requested ref was found after fetch
921 #
922 try:
923 self.GetRevisionId()
924 except ManifestInvalidRevisionError:
925 # if the ref is a tag. We can try fetching
926 # the tag manually as a last resort
927 #
928 rev = self.revisionExpr
929 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700930 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200931
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800932 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800933 self._InitMRef()
934 else:
935 self._InitMirrorHead()
936 try:
937 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
938 except OSError:
939 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800941
942 def PostRepoUpgrade(self):
943 self._InitHooks()
944
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 def _CopyFiles(self):
946 for file in self.copyfiles:
947 file._Copy()
948
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700949 def GetRevisionId(self, all=None):
950 if self.revisionId:
951 return self.revisionId
952
953 rem = self.GetRemote(self.remote.name)
954 rev = rem.ToLocal(self.revisionExpr)
955
956 if all is not None and rev in all:
957 return all[rev]
958
959 try:
960 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
961 except GitError:
962 raise ManifestInvalidRevisionError(
963 'revision %s in %s not found' % (self.revisionExpr,
964 self.name))
965
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700966 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 """Perform only the local IO portion of the sync process.
968 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700970 all = self.bare_ref.all
971 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700972 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800973
974 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700975 head = self.work_git.GetHead()
976 if head.startswith(R_HEADS):
977 branch = head[len(R_HEADS):]
978 try:
979 head = all[head]
980 except KeyError:
981 head = None
982 else:
983 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700985 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 # Currently on a detached HEAD. The user is assumed to
987 # not have any local modifications worth worrying about.
988 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700989 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700990 syncbuf.fail(self, _PriorSyncFailedError())
991 return
992
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700993 if head == revid:
994 # No changes; don't do anything further.
995 #
996 return
997
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700998 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001000 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001002 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001003 except GitError, e:
1004 syncbuf.fail(self, e)
1005 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001007 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001009 if head == revid:
1010 # No changes; don't do anything further.
1011 #
1012 return
1013
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001016 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 # The current branch has no tracking configuration.
1018 # Jump off it to a deatched HEAD.
1019 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001020 syncbuf.info(self,
1021 "leaving %s; does not track upstream",
1022 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001024 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001025 except GitError, e:
1026 syncbuf.fail(self, e)
1027 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001029 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001031 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001032 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001034 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 if not_merged:
1036 if upstream_gain:
1037 # The user has published this branch and some of those
1038 # commits are not yet merged upstream. We do not want
1039 # to rewrite the published commits so we punt.
1040 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001041 syncbuf.fail(self,
1042 "branch %s is published (but not merged) and is now %d commits behind"
1043 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001044 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001045 elif pub == head:
1046 # All published commits are merged, and thus we are a
1047 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001048 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001049 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001050 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001051 self._CopyFiles()
1052 syncbuf.later1(self, _doff)
1053 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001055 # Examine the local commits not in the remote. Find the
1056 # last one attributed to this user, if any.
1057 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001058 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001059 last_mine = None
1060 cnt_mine = 0
1061 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001062 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001063 if committer_email == self.UserEmail:
1064 last_mine = commit_id
1065 cnt_mine += 1
1066
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001067 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001068 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
1070 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 syncbuf.fail(self, _DirtyError())
1072 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001074 # If the upstream switched on us, warn the user.
1075 #
1076 if branch.merge != self.revisionExpr:
1077 if branch.merge and self.revisionExpr:
1078 syncbuf.info(self,
1079 'manifest switched %s...%s',
1080 branch.merge,
1081 self.revisionExpr)
1082 elif branch.merge:
1083 syncbuf.info(self,
1084 'manifest no longer tracks %s',
1085 branch.merge)
1086
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001087 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001089 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001091 syncbuf.info(self,
1092 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001093 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001095 branch.remote = self.GetRemote(self.remote.name)
1096 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 branch.Save()
1098
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001100 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001101 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 self._CopyFiles()
1103 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001104 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001106 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 self._CopyFiles()
1108 except GitError, e:
1109 syncbuf.fail(self, e)
1110 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001112 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 self._CopyFiles()
1115 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001117 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 # dest should already be an absolute path, but src is project relative
1119 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001120 abssrc = os.path.join(self.worktree, src)
1121 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001123 def DownloadPatchSet(self, change_id, patch_id):
1124 """Download a single patch set of a single change to FETCH_HEAD.
1125 """
1126 remote = self.GetRemote(self.remote.name)
1127
1128 cmd = ['fetch', remote.name]
1129 cmd.append('refs/changes/%2.2d/%d/%d' \
1130 % (change_id % 100, change_id, patch_id))
1131 cmd.extend(map(lambda x: str(x), remote.fetch))
1132 if GitCommand(self, cmd, bare=True).Wait() != 0:
1133 return None
1134 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001135 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001136 change_id,
1137 patch_id,
1138 self.bare_git.rev_parse('FETCH_HEAD'))
1139
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140
1141## Branch Management ##
1142
1143 def StartBranch(self, name):
1144 """Create a new branch off the manifest's revision.
1145 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001146 head = self.work_git.GetHead()
1147 if head == (R_HEADS + name):
1148 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001150 all = self.bare_ref.all
1151 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001152 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001153 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001154 capture_stdout = True,
1155 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001156
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001157 branch = self.GetBranch(name)
1158 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001159 branch.merge = self.revisionExpr
1160 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001161
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001162 if head.startswith(R_HEADS):
1163 try:
1164 head = all[head]
1165 except KeyError:
1166 head = None
1167
1168 if revid and head and revid == head:
1169 ref = os.path.join(self.gitdir, R_HEADS + name)
1170 try:
1171 os.makedirs(os.path.dirname(ref))
1172 except OSError:
1173 pass
1174 _lwrite(ref, '%s\n' % revid)
1175 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1176 'ref: %s%s\n' % (R_HEADS, name))
1177 branch.Save()
1178 return True
1179
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001180 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001181 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001182 capture_stdout = True,
1183 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001184 branch.Save()
1185 return True
1186 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
Wink Saville02d79452009-04-10 13:01:24 -07001188 def CheckoutBranch(self, name):
1189 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001190
1191 Args:
1192 name: The name of the branch to checkout.
1193
1194 Returns:
1195 True if the checkout succeeded; False if it didn't; None if the branch
1196 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001197 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001198 rev = R_HEADS + name
1199 head = self.work_git.GetHead()
1200 if head == rev:
1201 # Already on the branch
1202 #
1203 return True
Wink Saville02d79452009-04-10 13:01:24 -07001204
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001205 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001206 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001207 revid = all[rev]
1208 except KeyError:
1209 # Branch does not exist in this project
1210 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001211 return None
Wink Saville02d79452009-04-10 13:01:24 -07001212
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001213 if head.startswith(R_HEADS):
1214 try:
1215 head = all[head]
1216 except KeyError:
1217 head = None
1218
1219 if head == revid:
1220 # Same revision; just update HEAD to point to the new
1221 # target branch, but otherwise take no other action.
1222 #
1223 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1224 'ref: %s%s\n' % (R_HEADS, name))
1225 return True
1226
1227 return GitCommand(self,
1228 ['checkout', name, '--'],
1229 capture_stdout = True,
1230 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001231
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001232 def AbandonBranch(self, name):
1233 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001234
1235 Args:
1236 name: The name of the branch to abandon.
1237
1238 Returns:
1239 True if the abandon succeeded; False if it didn't; None if the branch
1240 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001241 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001242 rev = R_HEADS + name
1243 all = self.bare_ref.all
1244 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001245 # Doesn't exist
1246 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001247
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001248 head = self.work_git.GetHead()
1249 if head == rev:
1250 # We can't destroy the branch while we are sitting
1251 # on it. Switch to a detached HEAD.
1252 #
1253 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001254
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001255 revid = self.GetRevisionId(all)
1256 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001257 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1258 '%s\n' % revid)
1259 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001260 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001261
1262 return GitCommand(self,
1263 ['branch', '-D', name],
1264 capture_stdout = True,
1265 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001266
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 def PruneHeads(self):
1268 """Prune any topic branches already merged into upstream.
1269 """
1270 cb = self.CurrentBranch
1271 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001272 left = self._allrefs
1273 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 if name.startswith(R_HEADS):
1275 name = name[len(R_HEADS):]
1276 if cb is None or name != cb:
1277 kill.append(name)
1278
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001279 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 if cb is not None \
1281 and not self._revlist(HEAD + '...' + rev) \
1282 and not self.IsDirty(consider_untracked = False):
1283 self.work_git.DetachHead(HEAD)
1284 kill.append(cb)
1285
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001287 old = self.bare_git.GetHead()
1288 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 try:
1292 self.bare_git.DetachHead(rev)
1293
1294 b = ['branch', '-d']
1295 b.extend(kill)
1296 b = GitCommand(self, b, bare=True,
1297 capture_stdout=True,
1298 capture_stderr=True)
1299 b.Wait()
1300 finally:
1301 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001302 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001304 for branch in kill:
1305 if (R_HEADS + branch) not in left:
1306 self.CleanPublishedCache()
1307 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
1309 if cb and cb not in kill:
1310 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001311 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312
1313 kept = []
1314 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001315 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 branch = self.GetBranch(branch)
1317 base = branch.LocalMerge
1318 if not base:
1319 base = rev
1320 kept.append(ReviewableBranch(self, branch, base))
1321 return kept
1322
1323
1324## Direct Git Commands ##
1325
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001326 def _RemoteFetch(self, name=None, tag=None,
1327 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001328 quiet=False,
1329 alt_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 if not name:
1331 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001332
1333 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001334 remote = self.GetRemote(name)
1335 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001336 ssh_proxy = True
1337
Shawn O. Pearce88443382010-10-08 10:02:09 +02001338 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001339 if alt_dir and 'objects' == os.path.basename(alt_dir):
1340 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001341 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1342 remote = self.GetRemote(name)
1343
1344 all = self.bare_ref.all
1345 ids = set(all.values())
1346 tmp = set()
1347
1348 for r, id in GitRefs(ref_dir).all.iteritems():
1349 if r not in all:
1350 if r.startswith(R_TAGS) or remote.WritesTo(r):
1351 all[r] = id
1352 ids.add(id)
1353 continue
1354
1355 if id in ids:
1356 continue
1357
1358 r = 'refs/_alt/%s' % id
1359 all[r] = id
1360 ids.add(id)
1361 tmp.add(r)
1362
1363 ref_names = list(all.keys())
1364 ref_names.sort()
1365
1366 tmp_packed = ''
1367 old_packed = ''
1368
1369 for r in ref_names:
1370 line = '%s %s\n' % (all[r], r)
1371 tmp_packed += line
1372 if r not in tmp:
1373 old_packed += line
1374
1375 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001376 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001377 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001378
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001379 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001380
1381 # The --depth option only affects the initial fetch; after that we'll do
1382 # full fetches of changes.
1383 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1384 if depth and initial:
1385 cmd.append('--depth=%s' % depth)
1386
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001387 if quiet:
1388 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001389 if not self.worktree:
1390 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001391 cmd.append(name)
1392 if tag is not None:
1393 cmd.append('tag')
1394 cmd.append(tag)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001395
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001396 ok = False
1397 for i in range(2):
1398 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1399 ok = True
1400 break
1401 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001402
1403 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001404 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001405 if old_packed != '':
1406 _lwrite(packed_refs, old_packed)
1407 else:
1408 os.remove(packed_refs)
1409 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001410 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001411
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001412 def _ApplyCloneBundle(self, initial=False, quiet=False):
1413 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1414 return False
1415
1416 remote = self.GetRemote(self.remote.name)
1417 bundle_url = remote.url + '/clone.bundle'
1418 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1419 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1420 return False
1421
1422 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1423 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1424
1425 exist_dst = os.path.exists(bundle_dst)
1426 exist_tmp = os.path.exists(bundle_tmp)
1427
1428 if not initial and not exist_dst and not exist_tmp:
1429 return False
1430
1431 if not exist_dst:
1432 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1433 if not exist_dst:
1434 return False
1435
1436 cmd = ['fetch']
1437 if quiet:
1438 cmd.append('--quiet')
1439 if not self.worktree:
1440 cmd.append('--update-head-ok')
1441 cmd.append(bundle_dst)
1442 for f in remote.fetch:
1443 cmd.append(str(f))
1444 cmd.append('refs/tags/*:refs/tags/*')
1445
1446 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001447 if os.path.exists(bundle_dst):
1448 os.remove(bundle_dst)
1449 if os.path.exists(bundle_tmp):
1450 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001451 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001452
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001453 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001454 keep = True
1455 done = False
1456 dest = open(tmpPath, 'a+b')
1457 try:
1458 dest.seek(0, os.SEEK_END)
1459 pos = dest.tell()
1460
1461 req = urllib2.Request(srcUrl)
1462 if pos > 0:
1463 req.add_header('Range', 'bytes=%d-' % pos)
1464
1465 try:
1466 r = urllib2.urlopen(req)
1467 except urllib2.HTTPError, e:
1468 if e.code == 404:
1469 keep = False
1470 return False
1471 elif e.info()['content-type'] == 'text/plain':
1472 try:
1473 msg = e.read()
1474 if len(msg) > 0 and msg[-1] == '\n':
1475 msg = msg[0:-1]
1476 msg = ' (%s)' % msg
1477 except:
1478 msg = ''
1479 else:
1480 try:
1481 from BaseHTTPServer import BaseHTTPRequestHandler
1482 res = BaseHTTPRequestHandler.responses[e.code]
1483 msg = ' (%s: %s)' % (res[0], res[1])
1484 except:
1485 msg = ''
1486 raise DownloadError('HTTP %s%s' % (e.code, msg))
1487 except urllib2.URLError, e:
1488 raise DownloadError('%s (%s)' % (e.reason, req.get_host()))
1489
1490 p = None
1491 try:
1492 size = r.headers['content-length']
1493 unit = 1 << 10
1494
1495 if size and not quiet:
1496 if size > 1024 * 1.3:
1497 unit = 1 << 20
1498 desc = 'MB'
1499 else:
1500 desc = 'KB'
1501 p = Progress(
1502 'Downloading %s' % self.relpath,
1503 int(size) / unit,
1504 units=desc)
1505 if pos > 0:
1506 p.update(pos / unit)
1507
1508 s = 0
1509 while True:
1510 d = r.read(8192)
1511 if d == '':
1512 done = True
1513 return True
1514 dest.write(d)
1515 if p:
1516 s += len(d)
1517 if s >= unit:
1518 p.update(s / unit)
1519 s = s % unit
1520 if p:
1521 if s >= unit:
1522 p.update(s / unit)
1523 else:
1524 p.update(1)
1525 finally:
1526 r.close()
1527 if p:
1528 p.end()
1529 finally:
1530 dest.close()
1531
1532 if os.path.exists(dstPath):
1533 os.remove(dstPath)
1534 if done:
1535 os.rename(tmpPath, dstPath)
1536 elif not keep:
1537 os.remove(tmpPath)
1538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539 def _Checkout(self, rev, quiet=False):
1540 cmd = ['checkout']
1541 if quiet:
1542 cmd.append('-q')
1543 cmd.append(rev)
1544 cmd.append('--')
1545 if GitCommand(self, cmd).Wait() != 0:
1546 if self._allrefs:
1547 raise GitError('%s checkout %s ' % (self.name, rev))
1548
1549 def _ResetHard(self, rev, quiet=True):
1550 cmd = ['reset', '--hard']
1551 if quiet:
1552 cmd.append('-q')
1553 cmd.append(rev)
1554 if GitCommand(self, cmd).Wait() != 0:
1555 raise GitError('%s reset --hard %s ' % (self.name, rev))
1556
1557 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001558 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001559 if onto is not None:
1560 cmd.extend(['--onto', onto])
1561 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001562 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563 raise GitError('%s rebase %s ' % (self.name, upstream))
1564
1565 def _FastForward(self, head):
1566 cmd = ['merge', head]
1567 if GitCommand(self, cmd).Wait() != 0:
1568 raise GitError('%s merge %s ' % (self.name, head))
1569
1570 def _InitGitDir(self):
1571 if not os.path.exists(self.gitdir):
1572 os.makedirs(self.gitdir)
1573 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001574
Shawn O. Pearce88443382010-10-08 10:02:09 +02001575 mp = self.manifest.manifestProject
1576 ref_dir = mp.config.GetString('repo.reference')
1577
1578 if ref_dir:
1579 mirror_git = os.path.join(ref_dir, self.name + '.git')
1580 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1581 self.relpath + '.git')
1582
1583 if os.path.exists(mirror_git):
1584 ref_dir = mirror_git
1585
1586 elif os.path.exists(repo_git):
1587 ref_dir = repo_git
1588
1589 else:
1590 ref_dir = None
1591
1592 if ref_dir:
1593 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1594 os.path.join(ref_dir, 'objects') + '\n')
1595
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001596 if self.manifest.IsMirror:
1597 self.config.SetString('core.bare', 'true')
1598 else:
1599 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600
1601 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001602 try:
1603 to_rm = os.listdir(hooks)
1604 except OSError:
1605 to_rm = []
1606 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001607 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001608 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609
1610 m = self.manifest.manifestProject.config
1611 for key in ['user.name', 'user.email']:
1612 if m.Has(key, include_defaults = False):
1613 self.config.SetString(key, m.GetString(key))
1614
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001615 def _InitHooks(self):
1616 hooks = self._gitdir_path('hooks')
1617 if not os.path.exists(hooks):
1618 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001619 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001620 name = os.path.basename(stock_hook)
1621
Victor Boivie65e0f352011-04-18 11:23:29 +02001622 if name in ('commit-msg',) and not self.remote.review \
1623 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001624 # Don't install a Gerrit Code Review hook if this
1625 # project does not appear to use it for reviews.
1626 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001627 # Since the manifest project is one of those, but also
1628 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001629 continue
1630
1631 dst = os.path.join(hooks, name)
1632 if os.path.islink(dst):
1633 continue
1634 if os.path.exists(dst):
1635 if filecmp.cmp(stock_hook, dst, shallow=False):
1636 os.remove(dst)
1637 else:
1638 _error("%s: Not replacing %s hook", self.relpath, name)
1639 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001640 try:
1641 os.symlink(relpath(stock_hook, dst), dst)
1642 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001643 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001644 raise GitError('filesystem must support symlinks')
1645 else:
1646 raise
1647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001649 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001651 remote.url = self.remote.url
1652 remote.review = self.remote.review
1653 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001655 if self.worktree:
1656 remote.ResetFetch(mirror=False)
1657 else:
1658 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659 remote.Save()
1660
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661 def _InitMRef(self):
1662 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001663 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001665 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001666 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001667
1668 def _InitAnyMRef(self, ref):
1669 cur = self.bare_ref.symref(ref)
1670
1671 if self.revisionId:
1672 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1673 msg = 'manifest set to %s' % self.revisionId
1674 dst = self.revisionId + '^0'
1675 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1676 else:
1677 remote = self.GetRemote(self.remote.name)
1678 dst = remote.ToLocal(self.revisionExpr)
1679 if cur != dst:
1680 msg = 'manifest set to %s' % self.revisionExpr
1681 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 def _InitWorkTree(self):
1684 dotgit = os.path.join(self.worktree, '.git')
1685 if not os.path.exists(dotgit):
1686 os.makedirs(dotgit)
1687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688 for name in ['config',
1689 'description',
1690 'hooks',
1691 'info',
1692 'logs',
1693 'objects',
1694 'packed-refs',
1695 'refs',
1696 'rr-cache',
1697 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001698 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001699 src = os.path.join(self.gitdir, name)
1700 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001701 if os.path.islink(dst) or not os.path.exists(dst):
1702 os.symlink(relpath(src, dst), dst)
1703 else:
1704 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001705 except OSError, e:
1706 if e.errno == errno.EPERM:
1707 raise GitError('filesystem must support symlinks')
1708 else:
1709 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001711 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712
1713 cmd = ['read-tree', '--reset', '-u']
1714 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001715 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001716 if GitCommand(self, cmd).Wait() != 0:
1717 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001718
1719 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1720 if not os.path.exists(rr_cache):
1721 os.makedirs(rr_cache)
1722
Shawn O. Pearce93609662009-04-21 10:50:33 -07001723 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724
1725 def _gitdir_path(self, path):
1726 return os.path.join(self.gitdir, path)
1727
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001728 def _revlist(self, *args, **kw):
1729 a = []
1730 a.extend(args)
1731 a.append('--')
1732 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733
1734 @property
1735 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001736 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737
1738 class _GitGetByExec(object):
1739 def __init__(self, project, bare):
1740 self._project = project
1741 self._bare = bare
1742
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 def LsOthers(self):
1744 p = GitCommand(self._project,
1745 ['ls-files',
1746 '-z',
1747 '--others',
1748 '--exclude-standard'],
1749 bare = False,
1750 capture_stdout = True,
1751 capture_stderr = True)
1752 if p.Wait() == 0:
1753 out = p.stdout
1754 if out:
1755 return out[:-1].split("\0")
1756 return []
1757
1758 def DiffZ(self, name, *args):
1759 cmd = [name]
1760 cmd.append('-z')
1761 cmd.extend(args)
1762 p = GitCommand(self._project,
1763 cmd,
1764 bare = False,
1765 capture_stdout = True,
1766 capture_stderr = True)
1767 try:
1768 out = p.process.stdout.read()
1769 r = {}
1770 if out:
1771 out = iter(out[:-1].split('\0'))
1772 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001773 try:
1774 info = out.next()
1775 path = out.next()
1776 except StopIteration:
1777 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778
1779 class _Info(object):
1780 def __init__(self, path, omode, nmode, oid, nid, state):
1781 self.path = path
1782 self.src_path = None
1783 self.old_mode = omode
1784 self.new_mode = nmode
1785 self.old_id = oid
1786 self.new_id = nid
1787
1788 if len(state) == 1:
1789 self.status = state
1790 self.level = None
1791 else:
1792 self.status = state[:1]
1793 self.level = state[1:]
1794 while self.level.startswith('0'):
1795 self.level = self.level[1:]
1796
1797 info = info[1:].split(' ')
1798 info =_Info(path, *info)
1799 if info.status in ('R', 'C'):
1800 info.src_path = info.path
1801 info.path = out.next()
1802 r[info.path] = info
1803 return r
1804 finally:
1805 p.Wait()
1806
1807 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001808 if self._bare:
1809 path = os.path.join(self._project.gitdir, HEAD)
1810 else:
1811 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001812 fd = open(path, 'rb')
1813 try:
1814 line = fd.read()
1815 finally:
1816 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001817 if line.startswith('ref: '):
1818 return line[5:-1]
1819 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820
1821 def SetHead(self, ref, message=None):
1822 cmdv = []
1823 if message is not None:
1824 cmdv.extend(['-m', message])
1825 cmdv.append(HEAD)
1826 cmdv.append(ref)
1827 self.symbolic_ref(*cmdv)
1828
1829 def DetachHead(self, new, message=None):
1830 cmdv = ['--no-deref']
1831 if message is not None:
1832 cmdv.extend(['-m', message])
1833 cmdv.append(HEAD)
1834 cmdv.append(new)
1835 self.update_ref(*cmdv)
1836
1837 def UpdateRef(self, name, new, old=None,
1838 message=None,
1839 detach=False):
1840 cmdv = []
1841 if message is not None:
1842 cmdv.extend(['-m', message])
1843 if detach:
1844 cmdv.append('--no-deref')
1845 cmdv.append(name)
1846 cmdv.append(new)
1847 if old is not None:
1848 cmdv.append(old)
1849 self.update_ref(*cmdv)
1850
1851 def DeleteRef(self, name, old=None):
1852 if not old:
1853 old = self.rev_parse(name)
1854 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001855 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001856
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001857 def rev_list(self, *args, **kw):
1858 if 'format' in kw:
1859 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1860 else:
1861 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001862 cmdv.extend(args)
1863 p = GitCommand(self._project,
1864 cmdv,
1865 bare = self._bare,
1866 capture_stdout = True,
1867 capture_stderr = True)
1868 r = []
1869 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001870 if line[-1] == '\n':
1871 line = line[:-1]
1872 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001873 if p.Wait() != 0:
1874 raise GitError('%s rev-list %s: %s' % (
1875 self._project.name,
1876 str(args),
1877 p.stderr))
1878 return r
1879
1880 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001881 """Allow arbitrary git commands using pythonic syntax.
1882
1883 This allows you to do things like:
1884 git_obj.rev_parse('HEAD')
1885
1886 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1887 run. We'll replace the '_' with a '-' and try to run a git command.
1888 Any other arguments will be passed to the git command.
1889
1890 Args:
1891 name: The name of the git command to call. Any '_' characters will
1892 be replaced with '-'.
1893
1894 Returns:
1895 A callable object that will try to call git with the named command.
1896 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001897 name = name.replace('_', '-')
1898 def runner(*args):
1899 cmdv = [name]
1900 cmdv.extend(args)
1901 p = GitCommand(self._project,
1902 cmdv,
1903 bare = self._bare,
1904 capture_stdout = True,
1905 capture_stderr = True)
1906 if p.Wait() != 0:
1907 raise GitError('%s %s: %s' % (
1908 self._project.name,
1909 name,
1910 p.stderr))
1911 r = p.stdout
1912 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1913 return r[:-1]
1914 return r
1915 return runner
1916
1917
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001918class _PriorSyncFailedError(Exception):
1919 def __str__(self):
1920 return 'prior sync failed; rebase still in progress'
1921
1922class _DirtyError(Exception):
1923 def __str__(self):
1924 return 'contains uncommitted changes'
1925
1926class _InfoMessage(object):
1927 def __init__(self, project, text):
1928 self.project = project
1929 self.text = text
1930
1931 def Print(self, syncbuf):
1932 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1933 syncbuf.out.nl()
1934
1935class _Failure(object):
1936 def __init__(self, project, why):
1937 self.project = project
1938 self.why = why
1939
1940 def Print(self, syncbuf):
1941 syncbuf.out.fail('error: %s/: %s',
1942 self.project.relpath,
1943 str(self.why))
1944 syncbuf.out.nl()
1945
1946class _Later(object):
1947 def __init__(self, project, action):
1948 self.project = project
1949 self.action = action
1950
1951 def Run(self, syncbuf):
1952 out = syncbuf.out
1953 out.project('project %s/', self.project.relpath)
1954 out.nl()
1955 try:
1956 self.action()
1957 out.nl()
1958 return True
1959 except GitError, e:
1960 out.nl()
1961 return False
1962
1963class _SyncColoring(Coloring):
1964 def __init__(self, config):
1965 Coloring.__init__(self, config, 'reposync')
1966 self.project = self.printer('header', attr = 'bold')
1967 self.info = self.printer('info')
1968 self.fail = self.printer('fail', fg='red')
1969
1970class SyncBuffer(object):
1971 def __init__(self, config, detach_head=False):
1972 self._messages = []
1973 self._failures = []
1974 self._later_queue1 = []
1975 self._later_queue2 = []
1976
1977 self.out = _SyncColoring(config)
1978 self.out.redirect(sys.stderr)
1979
1980 self.detach_head = detach_head
1981 self.clean = True
1982
1983 def info(self, project, fmt, *args):
1984 self._messages.append(_InfoMessage(project, fmt % args))
1985
1986 def fail(self, project, err=None):
1987 self._failures.append(_Failure(project, err))
1988 self.clean = False
1989
1990 def later1(self, project, what):
1991 self._later_queue1.append(_Later(project, what))
1992
1993 def later2(self, project, what):
1994 self._later_queue2.append(_Later(project, what))
1995
1996 def Finish(self):
1997 self._PrintMessages()
1998 self._RunLater()
1999 self._PrintMessages()
2000 return self.clean
2001
2002 def _RunLater(self):
2003 for q in ['_later_queue1', '_later_queue2']:
2004 if not self._RunQueue(q):
2005 return
2006
2007 def _RunQueue(self, queue):
2008 for m in getattr(self, queue):
2009 if not m.Run(self):
2010 self.clean = False
2011 return False
2012 setattr(self, queue, [])
2013 return True
2014
2015 def _PrintMessages(self):
2016 for m in self._messages:
2017 m.Print(self)
2018 for m in self._failures:
2019 m.Print(self)
2020
2021 self._messages = []
2022 self._failures = []
2023
2024
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002025class MetaProject(Project):
2026 """A special project housed under .repo.
2027 """
2028 def __init__(self, manifest, name, gitdir, worktree):
2029 repodir = manifest.repodir
2030 Project.__init__(self,
2031 manifest = manifest,
2032 name = name,
2033 gitdir = gitdir,
2034 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002035 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002037 revisionExpr = 'refs/heads/master',
2038 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002039
2040 def PreSync(self):
2041 if self.Exists:
2042 cb = self.CurrentBranch
2043 if cb:
2044 base = self.GetBranch(cb).merge
2045 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002046 self.revisionExpr = base
2047 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002048
2049 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002050 def LastFetch(self):
2051 try:
2052 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2053 return os.path.getmtime(fh)
2054 except OSError:
2055 return 0
2056
2057 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058 def HasChanges(self):
2059 """Has the remote received new commits not yet checked out?
2060 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002061 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002062 return False
2063
2064 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002065 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002066 head = self.work_git.GetHead()
2067 if head.startswith(R_HEADS):
2068 try:
2069 head = all[head]
2070 except KeyError:
2071 head = None
2072
2073 if revid == head:
2074 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002075 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002076 return True
2077 return False