blob: 5f8369d541296d86ae28a86fef4d75b37e62e89c [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:
Shawn O. Pearce29472462011-10-11 09:24:07 -07001468 def _content_type():
1469 try:
1470 return e.info()['content-type']
1471 except:
1472 return None
1473
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001474 if e.code == 404:
1475 keep = False
1476 return False
Shawn O. Pearce29472462011-10-11 09:24:07 -07001477 elif _content_type() == 'text/plain':
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001478 try:
1479 msg = e.read()
1480 if len(msg) > 0 and msg[-1] == '\n':
1481 msg = msg[0:-1]
1482 msg = ' (%s)' % msg
1483 except:
1484 msg = ''
1485 else:
1486 try:
1487 from BaseHTTPServer import BaseHTTPRequestHandler
1488 res = BaseHTTPRequestHandler.responses[e.code]
1489 msg = ' (%s: %s)' % (res[0], res[1])
1490 except:
1491 msg = ''
1492 raise DownloadError('HTTP %s%s' % (e.code, msg))
1493 except urllib2.URLError, e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -07001494 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001495
1496 p = None
1497 try:
1498 size = r.headers['content-length']
1499 unit = 1 << 10
1500
1501 if size and not quiet:
1502 if size > 1024 * 1.3:
1503 unit = 1 << 20
1504 desc = 'MB'
1505 else:
1506 desc = 'KB'
1507 p = Progress(
1508 'Downloading %s' % self.relpath,
1509 int(size) / unit,
1510 units=desc)
1511 if pos > 0:
1512 p.update(pos / unit)
1513
1514 s = 0
1515 while True:
1516 d = r.read(8192)
1517 if d == '':
1518 done = True
1519 return True
1520 dest.write(d)
1521 if p:
1522 s += len(d)
1523 if s >= unit:
1524 p.update(s / unit)
1525 s = s % unit
1526 if p:
1527 if s >= unit:
1528 p.update(s / unit)
1529 else:
1530 p.update(1)
1531 finally:
1532 r.close()
1533 if p:
1534 p.end()
1535 finally:
1536 dest.close()
1537
1538 if os.path.exists(dstPath):
1539 os.remove(dstPath)
1540 if done:
1541 os.rename(tmpPath, dstPath)
1542 elif not keep:
1543 os.remove(tmpPath)
1544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001545 def _Checkout(self, rev, quiet=False):
1546 cmd = ['checkout']
1547 if quiet:
1548 cmd.append('-q')
1549 cmd.append(rev)
1550 cmd.append('--')
1551 if GitCommand(self, cmd).Wait() != 0:
1552 if self._allrefs:
1553 raise GitError('%s checkout %s ' % (self.name, rev))
1554
1555 def _ResetHard(self, rev, quiet=True):
1556 cmd = ['reset', '--hard']
1557 if quiet:
1558 cmd.append('-q')
1559 cmd.append(rev)
1560 if GitCommand(self, cmd).Wait() != 0:
1561 raise GitError('%s reset --hard %s ' % (self.name, rev))
1562
1563 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001564 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565 if onto is not None:
1566 cmd.extend(['--onto', onto])
1567 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001568 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 raise GitError('%s rebase %s ' % (self.name, upstream))
1570
1571 def _FastForward(self, head):
1572 cmd = ['merge', head]
1573 if GitCommand(self, cmd).Wait() != 0:
1574 raise GitError('%s merge %s ' % (self.name, head))
1575
1576 def _InitGitDir(self):
1577 if not os.path.exists(self.gitdir):
1578 os.makedirs(self.gitdir)
1579 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001580
Shawn O. Pearce88443382010-10-08 10:02:09 +02001581 mp = self.manifest.manifestProject
1582 ref_dir = mp.config.GetString('repo.reference')
1583
1584 if ref_dir:
1585 mirror_git = os.path.join(ref_dir, self.name + '.git')
1586 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1587 self.relpath + '.git')
1588
1589 if os.path.exists(mirror_git):
1590 ref_dir = mirror_git
1591
1592 elif os.path.exists(repo_git):
1593 ref_dir = repo_git
1594
1595 else:
1596 ref_dir = None
1597
1598 if ref_dir:
1599 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1600 os.path.join(ref_dir, 'objects') + '\n')
1601
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001602 if self.manifest.IsMirror:
1603 self.config.SetString('core.bare', 'true')
1604 else:
1605 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606
1607 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001608 try:
1609 to_rm = os.listdir(hooks)
1610 except OSError:
1611 to_rm = []
1612 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001613 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001614 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001615
1616 m = self.manifest.manifestProject.config
1617 for key in ['user.name', 'user.email']:
1618 if m.Has(key, include_defaults = False):
1619 self.config.SetString(key, m.GetString(key))
1620
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001621 def _InitHooks(self):
1622 hooks = self._gitdir_path('hooks')
1623 if not os.path.exists(hooks):
1624 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001625 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001626 name = os.path.basename(stock_hook)
1627
Victor Boivie65e0f352011-04-18 11:23:29 +02001628 if name in ('commit-msg',) and not self.remote.review \
1629 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001630 # Don't install a Gerrit Code Review hook if this
1631 # project does not appear to use it for reviews.
1632 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001633 # Since the manifest project is one of those, but also
1634 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001635 continue
1636
1637 dst = os.path.join(hooks, name)
1638 if os.path.islink(dst):
1639 continue
1640 if os.path.exists(dst):
1641 if filecmp.cmp(stock_hook, dst, shallow=False):
1642 os.remove(dst)
1643 else:
1644 _error("%s: Not replacing %s hook", self.relpath, name)
1645 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001646 try:
1647 os.symlink(relpath(stock_hook, dst), dst)
1648 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001649 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001650 raise GitError('filesystem must support symlinks')
1651 else:
1652 raise
1653
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001655 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001657 remote.url = self.remote.url
1658 remote.review = self.remote.review
1659 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001661 if self.worktree:
1662 remote.ResetFetch(mirror=False)
1663 else:
1664 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665 remote.Save()
1666
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 def _InitMRef(self):
1668 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001669 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001671 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001672 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001673
1674 def _InitAnyMRef(self, ref):
1675 cur = self.bare_ref.symref(ref)
1676
1677 if self.revisionId:
1678 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1679 msg = 'manifest set to %s' % self.revisionId
1680 dst = self.revisionId + '^0'
1681 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1682 else:
1683 remote = self.GetRemote(self.remote.name)
1684 dst = remote.ToLocal(self.revisionExpr)
1685 if cur != dst:
1686 msg = 'manifest set to %s' % self.revisionExpr
1687 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001688
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001689 def _InitWorkTree(self):
1690 dotgit = os.path.join(self.worktree, '.git')
1691 if not os.path.exists(dotgit):
1692 os.makedirs(dotgit)
1693
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 for name in ['config',
1695 'description',
1696 'hooks',
1697 'info',
1698 'logs',
1699 'objects',
1700 'packed-refs',
1701 'refs',
1702 'rr-cache',
1703 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001704 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001705 src = os.path.join(self.gitdir, name)
1706 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001707 if os.path.islink(dst) or not os.path.exists(dst):
1708 os.symlink(relpath(src, dst), dst)
1709 else:
1710 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001711 except OSError, e:
1712 if e.errno == errno.EPERM:
1713 raise GitError('filesystem must support symlinks')
1714 else:
1715 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001716
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001717 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718
1719 cmd = ['read-tree', '--reset', '-u']
1720 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001721 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001722 if GitCommand(self, cmd).Wait() != 0:
1723 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001724
1725 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1726 if not os.path.exists(rr_cache):
1727 os.makedirs(rr_cache)
1728
Shawn O. Pearce93609662009-04-21 10:50:33 -07001729 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730
1731 def _gitdir_path(self, path):
1732 return os.path.join(self.gitdir, path)
1733
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001734 def _revlist(self, *args, **kw):
1735 a = []
1736 a.extend(args)
1737 a.append('--')
1738 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739
1740 @property
1741 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001742 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743
1744 class _GitGetByExec(object):
1745 def __init__(self, project, bare):
1746 self._project = project
1747 self._bare = bare
1748
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749 def LsOthers(self):
1750 p = GitCommand(self._project,
1751 ['ls-files',
1752 '-z',
1753 '--others',
1754 '--exclude-standard'],
1755 bare = False,
1756 capture_stdout = True,
1757 capture_stderr = True)
1758 if p.Wait() == 0:
1759 out = p.stdout
1760 if out:
1761 return out[:-1].split("\0")
1762 return []
1763
1764 def DiffZ(self, name, *args):
1765 cmd = [name]
1766 cmd.append('-z')
1767 cmd.extend(args)
1768 p = GitCommand(self._project,
1769 cmd,
1770 bare = False,
1771 capture_stdout = True,
1772 capture_stderr = True)
1773 try:
1774 out = p.process.stdout.read()
1775 r = {}
1776 if out:
1777 out = iter(out[:-1].split('\0'))
1778 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001779 try:
1780 info = out.next()
1781 path = out.next()
1782 except StopIteration:
1783 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784
1785 class _Info(object):
1786 def __init__(self, path, omode, nmode, oid, nid, state):
1787 self.path = path
1788 self.src_path = None
1789 self.old_mode = omode
1790 self.new_mode = nmode
1791 self.old_id = oid
1792 self.new_id = nid
1793
1794 if len(state) == 1:
1795 self.status = state
1796 self.level = None
1797 else:
1798 self.status = state[:1]
1799 self.level = state[1:]
1800 while self.level.startswith('0'):
1801 self.level = self.level[1:]
1802
1803 info = info[1:].split(' ')
1804 info =_Info(path, *info)
1805 if info.status in ('R', 'C'):
1806 info.src_path = info.path
1807 info.path = out.next()
1808 r[info.path] = info
1809 return r
1810 finally:
1811 p.Wait()
1812
1813 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001814 if self._bare:
1815 path = os.path.join(self._project.gitdir, HEAD)
1816 else:
1817 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001818 fd = open(path, 'rb')
1819 try:
1820 line = fd.read()
1821 finally:
1822 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001823 if line.startswith('ref: '):
1824 return line[5:-1]
1825 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826
1827 def SetHead(self, ref, message=None):
1828 cmdv = []
1829 if message is not None:
1830 cmdv.extend(['-m', message])
1831 cmdv.append(HEAD)
1832 cmdv.append(ref)
1833 self.symbolic_ref(*cmdv)
1834
1835 def DetachHead(self, new, message=None):
1836 cmdv = ['--no-deref']
1837 if message is not None:
1838 cmdv.extend(['-m', message])
1839 cmdv.append(HEAD)
1840 cmdv.append(new)
1841 self.update_ref(*cmdv)
1842
1843 def UpdateRef(self, name, new, old=None,
1844 message=None,
1845 detach=False):
1846 cmdv = []
1847 if message is not None:
1848 cmdv.extend(['-m', message])
1849 if detach:
1850 cmdv.append('--no-deref')
1851 cmdv.append(name)
1852 cmdv.append(new)
1853 if old is not None:
1854 cmdv.append(old)
1855 self.update_ref(*cmdv)
1856
1857 def DeleteRef(self, name, old=None):
1858 if not old:
1859 old = self.rev_parse(name)
1860 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001861 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001862
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001863 def rev_list(self, *args, **kw):
1864 if 'format' in kw:
1865 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1866 else:
1867 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001868 cmdv.extend(args)
1869 p = GitCommand(self._project,
1870 cmdv,
1871 bare = self._bare,
1872 capture_stdout = True,
1873 capture_stderr = True)
1874 r = []
1875 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001876 if line[-1] == '\n':
1877 line = line[:-1]
1878 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001879 if p.Wait() != 0:
1880 raise GitError('%s rev-list %s: %s' % (
1881 self._project.name,
1882 str(args),
1883 p.stderr))
1884 return r
1885
1886 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001887 """Allow arbitrary git commands using pythonic syntax.
1888
1889 This allows you to do things like:
1890 git_obj.rev_parse('HEAD')
1891
1892 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1893 run. We'll replace the '_' with a '-' and try to run a git command.
1894 Any other arguments will be passed to the git command.
1895
1896 Args:
1897 name: The name of the git command to call. Any '_' characters will
1898 be replaced with '-'.
1899
1900 Returns:
1901 A callable object that will try to call git with the named command.
1902 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001903 name = name.replace('_', '-')
1904 def runner(*args):
1905 cmdv = [name]
1906 cmdv.extend(args)
1907 p = GitCommand(self._project,
1908 cmdv,
1909 bare = self._bare,
1910 capture_stdout = True,
1911 capture_stderr = True)
1912 if p.Wait() != 0:
1913 raise GitError('%s %s: %s' % (
1914 self._project.name,
1915 name,
1916 p.stderr))
1917 r = p.stdout
1918 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1919 return r[:-1]
1920 return r
1921 return runner
1922
1923
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001924class _PriorSyncFailedError(Exception):
1925 def __str__(self):
1926 return 'prior sync failed; rebase still in progress'
1927
1928class _DirtyError(Exception):
1929 def __str__(self):
1930 return 'contains uncommitted changes'
1931
1932class _InfoMessage(object):
1933 def __init__(self, project, text):
1934 self.project = project
1935 self.text = text
1936
1937 def Print(self, syncbuf):
1938 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1939 syncbuf.out.nl()
1940
1941class _Failure(object):
1942 def __init__(self, project, why):
1943 self.project = project
1944 self.why = why
1945
1946 def Print(self, syncbuf):
1947 syncbuf.out.fail('error: %s/: %s',
1948 self.project.relpath,
1949 str(self.why))
1950 syncbuf.out.nl()
1951
1952class _Later(object):
1953 def __init__(self, project, action):
1954 self.project = project
1955 self.action = action
1956
1957 def Run(self, syncbuf):
1958 out = syncbuf.out
1959 out.project('project %s/', self.project.relpath)
1960 out.nl()
1961 try:
1962 self.action()
1963 out.nl()
1964 return True
1965 except GitError, e:
1966 out.nl()
1967 return False
1968
1969class _SyncColoring(Coloring):
1970 def __init__(self, config):
1971 Coloring.__init__(self, config, 'reposync')
1972 self.project = self.printer('header', attr = 'bold')
1973 self.info = self.printer('info')
1974 self.fail = self.printer('fail', fg='red')
1975
1976class SyncBuffer(object):
1977 def __init__(self, config, detach_head=False):
1978 self._messages = []
1979 self._failures = []
1980 self._later_queue1 = []
1981 self._later_queue2 = []
1982
1983 self.out = _SyncColoring(config)
1984 self.out.redirect(sys.stderr)
1985
1986 self.detach_head = detach_head
1987 self.clean = True
1988
1989 def info(self, project, fmt, *args):
1990 self._messages.append(_InfoMessage(project, fmt % args))
1991
1992 def fail(self, project, err=None):
1993 self._failures.append(_Failure(project, err))
1994 self.clean = False
1995
1996 def later1(self, project, what):
1997 self._later_queue1.append(_Later(project, what))
1998
1999 def later2(self, project, what):
2000 self._later_queue2.append(_Later(project, what))
2001
2002 def Finish(self):
2003 self._PrintMessages()
2004 self._RunLater()
2005 self._PrintMessages()
2006 return self.clean
2007
2008 def _RunLater(self):
2009 for q in ['_later_queue1', '_later_queue2']:
2010 if not self._RunQueue(q):
2011 return
2012
2013 def _RunQueue(self, queue):
2014 for m in getattr(self, queue):
2015 if not m.Run(self):
2016 self.clean = False
2017 return False
2018 setattr(self, queue, [])
2019 return True
2020
2021 def _PrintMessages(self):
2022 for m in self._messages:
2023 m.Print(self)
2024 for m in self._failures:
2025 m.Print(self)
2026
2027 self._messages = []
2028 self._failures = []
2029
2030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002031class MetaProject(Project):
2032 """A special project housed under .repo.
2033 """
2034 def __init__(self, manifest, name, gitdir, worktree):
2035 repodir = manifest.repodir
2036 Project.__init__(self,
2037 manifest = manifest,
2038 name = name,
2039 gitdir = gitdir,
2040 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002041 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002043 revisionExpr = 'refs/heads/master',
2044 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002045
2046 def PreSync(self):
2047 if self.Exists:
2048 cb = self.CurrentBranch
2049 if cb:
2050 base = self.GetBranch(cb).merge
2051 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002052 self.revisionExpr = base
2053 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002054
2055 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002056 def LastFetch(self):
2057 try:
2058 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2059 return os.path.getmtime(fh)
2060 except OSError:
2061 return 0
2062
2063 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002064 def HasChanges(self):
2065 """Has the remote received new commits not yet checked out?
2066 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002067 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002068 return False
2069
2070 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002071 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002072 head = self.work_git.GetHead()
2073 if head.startswith(R_HEADS):
2074 try:
2075 head = all[head]
2076 except KeyError:
2077 head = None
2078
2079 if revid == head:
2080 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002081 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002082 return True
2083 return False