blob: 4bc54de920675ed0232efdab0ac8b8cc2a2aee38 [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
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070027try:
28 import threading as _threading
29except ImportError:
30 import dummy_threading as _threading
31
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032from color import Coloring
33from git_command import GitCommand
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070034from git_config import GitConfig, IsId, GetSchemeFromUrl
35from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080036from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080037from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070038from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070042_urllib_lock = _threading.Lock()
43
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070044def _lwrite(path, content):
45 lock = '%s.lock' % path
46
47 fd = open(lock, 'wb')
48 try:
49 fd.write(content)
50 finally:
51 fd.close()
52
53 try:
54 os.rename(lock, path)
55 except OSError:
56 os.remove(lock)
57 raise
58
Shawn O. Pearce48244782009-04-16 08:25:57 -070059def _error(fmt, *args):
60 msg = fmt % args
61 print >>sys.stderr, 'error: %s' % msg
62
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063def not_rev(r):
64 return '^' + r
65
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080066def sq(r):
67 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080068
Doug Anderson8ced8642011-01-10 14:16:30 -080069_project_hook_list = None
70def _ProjectHooks():
71 """List the hooks present in the 'hooks' directory.
72
73 These hooks are project hooks and are copied to the '.git/hooks' directory
74 of all subprojects.
75
76 This function caches the list of hooks (based on the contents of the
77 'repo/hooks' directory) on the first call.
78
79 Returns:
80 A list of absolute paths to all of the files in the hooks directory.
81 """
82 global _project_hook_list
83 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080084 d = os.path.abspath(os.path.dirname(__file__))
85 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080086 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
87 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
89def relpath(dst, src):
90 src = os.path.dirname(src)
91 top = os.path.commonprefix([dst, src])
92 if top.endswith('/'):
93 top = top[:-1]
94 else:
95 top = os.path.dirname(top)
96
97 tmp = src
98 rel = ''
99 while top != tmp:
100 rel += '../'
101 tmp = os.path.dirname(tmp)
102 return rel + dst[len(top) + 1:]
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
118 self._commit_cache = self.project.bare_git.rev_list(
119 '--abbrev=8',
120 '--abbrev-commit',
121 '--pretty=oneline',
122 '--reverse',
123 '--date-order',
124 not_rev(self.base),
125 self.commit,
126 '--')
127 return self._commit_cache
128
129
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130class ReviewableBranch(object):
131 _commit_cache = None
132
133 def __init__(self, project, branch, base):
134 self.project = project
135 self.branch = branch
136 self.base = base
137
138 @property
139 def name(self):
140 return self.branch.name
141
142 @property
143 def commits(self):
144 if self._commit_cache is None:
145 self._commit_cache = self.project.bare_git.rev_list(
146 '--abbrev=8',
147 '--abbrev-commit',
148 '--pretty=oneline',
149 '--reverse',
150 '--date-order',
151 not_rev(self.base),
152 R_HEADS + self.name,
153 '--')
154 return self._commit_cache
155
156 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800157 def unabbrev_commits(self):
158 r = dict()
159 for commit in self.project.bare_git.rev_list(
160 not_rev(self.base),
161 R_HEADS + self.name,
162 '--'):
163 r[commit[0:8]] = commit
164 return r
165
166 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167 def date(self):
168 return self.project.bare_git.log(
169 '--pretty=format:%cd',
170 '-n', '1',
171 R_HEADS + self.name,
172 '--')
173
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700174 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800175 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700176 people,
177 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700179 def GetPublishedRefs(self):
180 refs = {}
181 output = self.project.bare_git.ls_remote(
182 self.branch.remote.SshReviewUrl(self.project.UserEmail),
183 'refs/changes/*')
184 for line in output.split('\n'):
185 try:
186 (sha, ref) = line.split()
187 refs[sha] = ref
188 except ValueError:
189 pass
190
191 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192
193class StatusColoring(Coloring):
194 def __init__(self, config):
195 Coloring.__init__(self, config, 'status')
196 self.project = self.printer('header', attr = 'bold')
197 self.branch = self.printer('header', attr = 'bold')
198 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700199 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200
201 self.added = self.printer('added', fg = 'green')
202 self.changed = self.printer('changed', fg = 'red')
203 self.untracked = self.printer('untracked', fg = 'red')
204
205
206class DiffColoring(Coloring):
207 def __init__(self, config):
208 Coloring.__init__(self, config, 'diff')
209 self.project = self.printer('header', attr = 'bold')
210
211
212class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800213 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 self.src = src
215 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800216 self.abs_src = abssrc
217 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218
219 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800220 src = self.abs_src
221 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 # copy file if it does not exist or is out of date
223 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
224 try:
225 # remove existing file first, since it might be read-only
226 if os.path.exists(dest):
227 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400228 else:
229 dir = os.path.dirname(dest)
230 if not os.path.isdir(dir):
231 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232 shutil.copy(src, dest)
233 # make the file read-only
234 mode = os.stat(dest)[stat.ST_MODE]
235 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
236 os.chmod(dest, mode)
237 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700238 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700240class RemoteSpec(object):
241 def __init__(self,
242 name,
243 url = None,
244 review = None):
245 self.name = name
246 self.url = url
247 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248
Doug Anderson37282b42011-03-04 11:54:18 -0800249class RepoHook(object):
250 """A RepoHook contains information about a script to run as a hook.
251
252 Hooks are used to run a python script before running an upload (for instance,
253 to run presubmit checks). Eventually, we may have hooks for other actions.
254
255 This shouldn't be confused with files in the 'repo/hooks' directory. Those
256 files are copied into each '.git/hooks' folder for each project. Repo-level
257 hooks are associated instead with repo actions.
258
259 Hooks are always python. When a hook is run, we will load the hook into the
260 interpreter and execute its main() function.
261 """
262 def __init__(self,
263 hook_type,
264 hooks_project,
265 topdir,
266 abort_if_user_denies=False):
267 """RepoHook constructor.
268
269 Params:
270 hook_type: A string representing the type of hook. This is also used
271 to figure out the name of the file containing the hook. For
272 example: 'pre-upload'.
273 hooks_project: The project containing the repo hooks. If you have a
274 manifest, this is manifest.repo_hooks_project. OK if this is None,
275 which will make the hook a no-op.
276 topdir: Repo's top directory (the one containing the .repo directory).
277 Scripts will run with CWD as this directory. If you have a manifest,
278 this is manifest.topdir
279 abort_if_user_denies: If True, we'll throw a HookError() if the user
280 doesn't allow us to run the hook.
281 """
282 self._hook_type = hook_type
283 self._hooks_project = hooks_project
284 self._topdir = topdir
285 self._abort_if_user_denies = abort_if_user_denies
286
287 # Store the full path to the script for convenience.
288 if self._hooks_project:
289 self._script_fullpath = os.path.join(self._hooks_project.worktree,
290 self._hook_type + '.py')
291 else:
292 self._script_fullpath = None
293
294 def _GetHash(self):
295 """Return a hash of the contents of the hooks directory.
296
297 We'll just use git to do this. This hash has the property that if anything
298 changes in the directory we will return a different has.
299
300 SECURITY CONSIDERATION:
301 This hash only represents the contents of files in the hook directory, not
302 any other files imported or called by hooks. Changes to imported files
303 can change the script behavior without affecting the hash.
304
305 Returns:
306 A string representing the hash. This will always be ASCII so that it can
307 be printed to the user easily.
308 """
309 assert self._hooks_project, "Must have hooks to calculate their hash."
310
311 # We will use the work_git object rather than just calling GetRevisionId().
312 # That gives us a hash of the latest checked in version of the files that
313 # the user will actually be executing. Specifically, GetRevisionId()
314 # doesn't appear to change even if a user checks out a different version
315 # of the hooks repo (via git checkout) nor if a user commits their own revs.
316 #
317 # NOTE: Local (non-committed) changes will not be factored into this hash.
318 # I think this is OK, since we're really only worried about warning the user
319 # about upstream changes.
320 return self._hooks_project.work_git.rev_parse('HEAD')
321
322 def _GetMustVerb(self):
323 """Return 'must' if the hook is required; 'should' if not."""
324 if self._abort_if_user_denies:
325 return 'must'
326 else:
327 return 'should'
328
329 def _CheckForHookApproval(self):
330 """Check to see whether this hook has been approved.
331
332 We'll look at the hash of all of the hooks. If this matches the hash that
333 the user last approved, we're done. If it doesn't, we'll ask the user
334 about approval.
335
336 Note that we ask permission for each individual hook even though we use
337 the hash of all hooks when detecting changes. We'd like the user to be
338 able to approve / deny each hook individually. We only use the hash of all
339 hooks because there is no other easy way to detect changes to local imports.
340
341 Returns:
342 True if this hook is approved to run; False otherwise.
343
344 Raises:
345 HookError: Raised if the user doesn't approve and abort_if_user_denies
346 was passed to the consturctor.
347 """
348 hooks_dir = self._hooks_project.worktree
349 hooks_config = self._hooks_project.config
350 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
351
352 # Get the last hash that the user approved for this hook; may be None.
353 old_hash = hooks_config.GetString(git_approval_key)
354
355 # Get the current hash so we can tell if scripts changed since approval.
356 new_hash = self._GetHash()
357
358 if old_hash is not None:
359 # User previously approved hook and asked not to be prompted again.
360 if new_hash == old_hash:
361 # Approval matched. We're done.
362 return True
363 else:
364 # Give the user a reason why we're prompting, since they last told
365 # us to "never ask again".
366 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
367 self._hook_type)
368 else:
369 prompt = ''
370
371 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
372 if sys.stdout.isatty():
373 prompt += ('Repo %s run the script:\n'
374 ' %s\n'
375 '\n'
376 'Do you want to allow this script to run '
377 '(yes/yes-never-ask-again/NO)? ') % (
378 self._GetMustVerb(), self._script_fullpath)
379 response = raw_input(prompt).lower()
380 print
381
382 # User is doing a one-time approval.
383 if response in ('y', 'yes'):
384 return True
385 elif response == 'yes-never-ask-again':
386 hooks_config.SetString(git_approval_key, new_hash)
387 return True
388
389 # For anything else, we'll assume no approval.
390 if self._abort_if_user_denies:
391 raise HookError('You must allow the %s hook or use --no-verify.' %
392 self._hook_type)
393
394 return False
395
396 def _ExecuteHook(self, **kwargs):
397 """Actually execute the given hook.
398
399 This will run the hook's 'main' function in our python interpreter.
400
401 Args:
402 kwargs: Keyword arguments to pass to the hook. These are often specific
403 to the hook type. For instance, pre-upload hooks will contain
404 a project_list.
405 """
406 # Keep sys.path and CWD stashed away so that we can always restore them
407 # upon function exit.
408 orig_path = os.getcwd()
409 orig_syspath = sys.path
410
411 try:
412 # Always run hooks with CWD as topdir.
413 os.chdir(self._topdir)
414
415 # Put the hook dir as the first item of sys.path so hooks can do
416 # relative imports. We want to replace the repo dir as [0] so
417 # hooks can't import repo files.
418 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
419
420 # Exec, storing global context in the context dict. We catch exceptions
421 # and convert to a HookError w/ just the failing traceback.
422 context = {}
423 try:
424 execfile(self._script_fullpath, context)
425 except Exception:
426 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
427 traceback.format_exc(), self._hook_type))
428
429 # Running the script should have defined a main() function.
430 if 'main' not in context:
431 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
432
433
434 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
435 # We don't actually want hooks to define their main with this argument--
436 # it's there to remind them that their hook should always take **kwargs.
437 # For instance, a pre-upload hook should be defined like:
438 # def main(project_list, **kwargs):
439 #
440 # This allows us to later expand the API without breaking old hooks.
441 kwargs = kwargs.copy()
442 kwargs['hook_should_take_kwargs'] = True
443
444 # Call the main function in the hook. If the hook should cause the
445 # build to fail, it will raise an Exception. We'll catch that convert
446 # to a HookError w/ just the failing traceback.
447 try:
448 context['main'](**kwargs)
449 except Exception:
450 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
451 'above.' % (
452 traceback.format_exc(), self._hook_type))
453 finally:
454 # Restore sys.path and CWD.
455 sys.path = orig_syspath
456 os.chdir(orig_path)
457
458 def Run(self, user_allows_all_hooks, **kwargs):
459 """Run the hook.
460
461 If the hook doesn't exist (because there is no hooks project or because
462 this particular hook is not enabled), this is a no-op.
463
464 Args:
465 user_allows_all_hooks: If True, we will never prompt about running the
466 hook--we'll just assume it's OK to run it.
467 kwargs: Keyword arguments to pass to the hook. These are often specific
468 to the hook type. For instance, pre-upload hooks will contain
469 a project_list.
470
471 Raises:
472 HookError: If there was a problem finding the hook or the user declined
473 to run a required hook (from _CheckForHookApproval).
474 """
475 # No-op if there is no hooks project or if hook is disabled.
476 if ((not self._hooks_project) or
477 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
478 return
479
480 # Bail with a nice error if we can't find the hook.
481 if not os.path.isfile(self._script_fullpath):
482 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
483
484 # Make sure the user is OK with running the hook.
485 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
486 return
487
488 # Run the hook with the same version of python we're using.
489 self._ExecuteHook(**kwargs)
490
491
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700492class Project(object):
493 def __init__(self,
494 manifest,
495 name,
496 remote,
497 gitdir,
498 worktree,
499 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700500 revisionExpr,
501 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700502 self.manifest = manifest
503 self.name = name
504 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800505 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800506 if worktree:
507 self.worktree = worktree.replace('\\', '/')
508 else:
509 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700510 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700511 self.revisionExpr = revisionExpr
512
513 if revisionId is None \
514 and revisionExpr \
515 and IsId(revisionExpr):
516 self.revisionId = revisionExpr
517 else:
518 self.revisionId = revisionId
519
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700521 self.copyfiles = []
522 self.config = GitConfig.ForRepository(
523 gitdir = self.gitdir,
524 defaults = self.manifest.globalConfig)
525
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800526 if self.worktree:
527 self.work_git = self._GitGetByExec(self, bare=False)
528 else:
529 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700531 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700532
Doug Anderson37282b42011-03-04 11:54:18 -0800533 # This will be filled in if a project is later identified to be the
534 # project containing repo hooks.
535 self.enabled_repo_hooks = []
536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 @property
538 def Exists(self):
539 return os.path.isdir(self.gitdir)
540
541 @property
542 def CurrentBranch(self):
543 """Obtain the name of the currently checked out branch.
544 The branch name omits the 'refs/heads/' prefix.
545 None is returned if the project is on a detached HEAD.
546 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700547 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 if b.startswith(R_HEADS):
549 return b[len(R_HEADS):]
550 return None
551
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700552 def IsRebaseInProgress(self):
553 w = self.worktree
554 g = os.path.join(w, '.git')
555 return os.path.exists(os.path.join(g, 'rebase-apply')) \
556 or os.path.exists(os.path.join(g, 'rebase-merge')) \
557 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 def IsDirty(self, consider_untracked=True):
560 """Is the working directory modified in some way?
561 """
562 self.work_git.update_index('-q',
563 '--unmerged',
564 '--ignore-missing',
565 '--refresh')
566 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
567 return True
568 if self.work_git.DiffZ('diff-files'):
569 return True
570 if consider_untracked and self.work_git.LsOthers():
571 return True
572 return False
573
574 _userident_name = None
575 _userident_email = None
576
577 @property
578 def UserName(self):
579 """Obtain the user's personal name.
580 """
581 if self._userident_name is None:
582 self._LoadUserIdentity()
583 return self._userident_name
584
585 @property
586 def UserEmail(self):
587 """Obtain the user's email address. This is very likely
588 to be their Gerrit login.
589 """
590 if self._userident_email is None:
591 self._LoadUserIdentity()
592 return self._userident_email
593
594 def _LoadUserIdentity(self):
595 u = self.bare_git.var('GIT_COMMITTER_IDENT')
596 m = re.compile("^(.*) <([^>]*)> ").match(u)
597 if m:
598 self._userident_name = m.group(1)
599 self._userident_email = m.group(2)
600 else:
601 self._userident_name = ''
602 self._userident_email = ''
603
604 def GetRemote(self, name):
605 """Get the configuration for a single remote.
606 """
607 return self.config.GetRemote(name)
608
609 def GetBranch(self, name):
610 """Get the configuration for a single branch.
611 """
612 return self.config.GetBranch(name)
613
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700614 def GetBranches(self):
615 """Get all existing local branches.
616 """
617 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700618 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700619 heads = {}
620 pubd = {}
621
622 for name, id in all.iteritems():
623 if name.startswith(R_HEADS):
624 name = name[len(R_HEADS):]
625 b = self.GetBranch(name)
626 b.current = name == current
627 b.published = None
628 b.revision = id
629 heads[name] = b
630
631 for name, id in all.iteritems():
632 if name.startswith(R_PUB):
633 name = name[len(R_PUB):]
634 b = heads.get(name)
635 if b:
636 b.published = id
637
638 return heads
639
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640
641## Status Display ##
642
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500643 def HasChanges(self):
644 """Returns true if there are uncommitted changes.
645 """
646 self.work_git.update_index('-q',
647 '--unmerged',
648 '--ignore-missing',
649 '--refresh')
650 if self.IsRebaseInProgress():
651 return True
652
653 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
654 return True
655
656 if self.work_git.DiffZ('diff-files'):
657 return True
658
659 if self.work_git.LsOthers():
660 return True
661
662 return False
663
Terence Haddock4655e812011-03-31 12:33:34 +0200664 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700665 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200666
667 Args:
668 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669 """
670 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200671 if output_redir == None:
672 output_redir = sys.stdout
673 print >>output_redir, ''
674 print >>output_redir, 'project %s/' % self.relpath
675 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700676 return
677
678 self.work_git.update_index('-q',
679 '--unmerged',
680 '--ignore-missing',
681 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700682 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
684 df = self.work_git.DiffZ('diff-files')
685 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700686 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700687 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688
689 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200690 if not output_redir == None:
691 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692 out.project('project %-40s', self.relpath + '/')
693
694 branch = self.CurrentBranch
695 if branch is None:
696 out.nobranch('(*** NO BRANCH ***)')
697 else:
698 out.branch('branch %s', branch)
699 out.nl()
700
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700701 if rb:
702 out.important('prior sync failed; rebase still in progress')
703 out.nl()
704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 paths = list()
706 paths.extend(di.keys())
707 paths.extend(df.keys())
708 paths.extend(do)
709
710 paths = list(set(paths))
711 paths.sort()
712
713 for p in paths:
714 try: i = di[p]
715 except KeyError: i = None
716
717 try: f = df[p]
718 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 if i: i_status = i.status.upper()
721 else: i_status = '-'
722
723 if f: f_status = f.status.lower()
724 else: f_status = '-'
725
726 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800727 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728 i.src_path, p, i.level)
729 else:
730 line = ' %s%s\t%s' % (i_status, f_status, p)
731
732 if i and not f:
733 out.added('%s', line)
734 elif (i and f) or (not i and f):
735 out.changed('%s', line)
736 elif not i and not f:
737 out.untracked('%s', line)
738 else:
739 out.write('%s', line)
740 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200741
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700742 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
744 def PrintWorkTreeDiff(self):
745 """Prints the status of the repository to stdout.
746 """
747 out = DiffColoring(self.config)
748 cmd = ['diff']
749 if out.is_on:
750 cmd.append('--color')
751 cmd.append(HEAD)
752 cmd.append('--')
753 p = GitCommand(self,
754 cmd,
755 capture_stdout = True,
756 capture_stderr = True)
757 has_diff = False
758 for line in p.process.stdout:
759 if not has_diff:
760 out.nl()
761 out.project('project %s/' % self.relpath)
762 out.nl()
763 has_diff = True
764 print line[:-1]
765 p.Wait()
766
767
768## Publish / Upload ##
769
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700770 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771 """Was the branch published (uploaded) for code review?
772 If so, returns the SHA-1 hash of the last published
773 state for the branch.
774 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700775 key = R_PUB + branch
776 if all is None:
777 try:
778 return self.bare_git.rev_parse(key)
779 except GitError:
780 return None
781 else:
782 try:
783 return all[key]
784 except KeyError:
785 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700787 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 """Prunes any stale published refs.
789 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700790 if all is None:
791 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792 heads = set()
793 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700794 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 if name.startswith(R_HEADS):
796 heads.add(name)
797 elif name.startswith(R_PUB):
798 canrm[name] = id
799
800 for name, id in canrm.iteritems():
801 n = name[len(R_PUB):]
802 if R_HEADS + n not in heads:
803 self.bare_git.DeleteRef(name, id)
804
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700805 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806 """List any branches which can be uploaded for review.
807 """
808 heads = {}
809 pubed = {}
810
811 for name, id in self._allrefs.iteritems():
812 if name.startswith(R_HEADS):
813 heads[name[len(R_HEADS):]] = id
814 elif name.startswith(R_PUB):
815 pubed[name[len(R_PUB):]] = id
816
817 ready = []
818 for branch, id in heads.iteritems():
819 if branch in pubed and pubed[branch] == id:
820 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700821 if selected_branch and branch != selected_branch:
822 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800824 rb = self.GetUploadableBranch(branch)
825 if rb:
826 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 return ready
828
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800829 def GetUploadableBranch(self, branch_name):
830 """Get a single uploadable branch, or None.
831 """
832 branch = self.GetBranch(branch_name)
833 base = branch.LocalMerge
834 if branch.LocalMerge:
835 rb = ReviewableBranch(self, branch, base)
836 if rb.commits:
837 return rb
838 return None
839
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700840 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700841 people=([],[]),
842 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843 """Uploads the named branch for code review.
844 """
845 if branch is None:
846 branch = self.CurrentBranch
847 if branch is None:
848 raise GitError('not currently on a branch')
849
850 branch = self.GetBranch(branch)
851 if not branch.LocalMerge:
852 raise GitError('branch %s does not track a remote' % branch.name)
853 if not branch.remote.review:
854 raise GitError('remote %s has no review url' % branch.remote.name)
855
856 dest_branch = branch.merge
857 if not dest_branch.startswith(R_HEADS):
858 dest_branch = R_HEADS + dest_branch
859
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800860 if not branch.remote.projectname:
861 branch.remote.projectname = self.name
862 branch.remote.Save()
863
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800864 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800865 if dest_branch.startswith(R_HEADS):
866 dest_branch = dest_branch[len(R_HEADS):]
867
868 rp = ['gerrit receive-pack']
869 for e in people[0]:
870 rp.append('--reviewer=%s' % sq(e))
871 for e in people[1]:
872 rp.append('--cc=%s' % sq(e))
873
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700874 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
875 if auto_topic:
876 ref_spec = ref_spec + '/' + branch.name
877
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800878 cmd = ['push']
879 cmd.append('--receive-pack=%s' % " ".join(rp))
880 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700881 cmd.append(ref_spec)
882
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800883 if GitCommand(self, cmd, bare = True).Wait() != 0:
884 raise UploadError('Upload failed')
885
886 else:
887 raise UploadError('Unsupported protocol %s' \
888 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889
890 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
891 self.bare_git.UpdateRef(R_PUB + branch.name,
892 R_HEADS + branch.name,
893 message = msg)
894
895
896## Sync ##
897
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700898 def Sync_NetworkHalf(self, quiet=False, is_new=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 """Perform only the network IO portion of the sync process.
900 Local working directory/branch state is not affected.
901 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700902 if is_new is None:
903 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200904 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 self._InitGitDir()
906 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700907
908 if is_new:
909 alt = os.path.join(self.gitdir, 'objects/info/alternates')
910 try:
911 fd = open(alt, 'rb')
912 try:
913 alt_dir = fd.readline().rstrip()
914 finally:
915 fd.close()
916 except IOError:
917 alt_dir = None
918 else:
919 alt_dir = None
920
921 if alt_dir is None and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
922 is_new = False
923
924 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800926
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200927 #Check that the requested ref was found after fetch
928 #
929 try:
930 self.GetRevisionId()
931 except ManifestInvalidRevisionError:
932 # if the ref is a tag. We can try fetching
933 # the tag manually as a last resort
934 #
935 rev = self.revisionExpr
936 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700937 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200938
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800939 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800940 self._InitMRef()
941 else:
942 self._InitMirrorHead()
943 try:
944 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
945 except OSError:
946 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800948
949 def PostRepoUpgrade(self):
950 self._InitHooks()
951
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 def _CopyFiles(self):
953 for file in self.copyfiles:
954 file._Copy()
955
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700956 def GetRevisionId(self, all=None):
957 if self.revisionId:
958 return self.revisionId
959
960 rem = self.GetRemote(self.remote.name)
961 rev = rem.ToLocal(self.revisionExpr)
962
963 if all is not None and rev in all:
964 return all[rev]
965
966 try:
967 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
968 except GitError:
969 raise ManifestInvalidRevisionError(
970 'revision %s in %s not found' % (self.revisionExpr,
971 self.name))
972
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700973 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974 """Perform only the local IO portion of the sync process.
975 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700977 all = self.bare_ref.all
978 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700979 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800980
981 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700982 head = self.work_git.GetHead()
983 if head.startswith(R_HEADS):
984 branch = head[len(R_HEADS):]
985 try:
986 head = all[head]
987 except KeyError:
988 head = None
989 else:
990 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700992 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993 # Currently on a detached HEAD. The user is assumed to
994 # not have any local modifications worth worrying about.
995 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700996 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700997 syncbuf.fail(self, _PriorSyncFailedError())
998 return
999
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001000 if head == revid:
1001 # No changes; don't do anything further.
1002 #
1003 return
1004
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001005 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001007 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001009 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001010 except GitError, e:
1011 syncbuf.fail(self, e)
1012 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001014 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001016 if head == revid:
1017 # No changes; don't do anything further.
1018 #
1019 return
1020
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001021 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001023 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 # The current branch has no tracking configuration.
1025 # Jump off it to a deatched HEAD.
1026 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001027 syncbuf.info(self,
1028 "leaving %s; does not track upstream",
1029 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001031 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 except GitError, e:
1033 syncbuf.fail(self, e)
1034 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001036 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001038 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001039 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001041 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 if not_merged:
1043 if upstream_gain:
1044 # The user has published this branch and some of those
1045 # commits are not yet merged upstream. We do not want
1046 # to rewrite the published commits so we punt.
1047 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001048 syncbuf.fail(self,
1049 "branch %s is published (but not merged) and is now %d commits behind"
1050 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001051 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001052 elif pub == head:
1053 # All published commits are merged, and thus we are a
1054 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001055 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001057 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001058 self._CopyFiles()
1059 syncbuf.later1(self, _doff)
1060 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001062 # Examine the local commits not in the remote. Find the
1063 # last one attributed to this user, if any.
1064 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001065 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001066 last_mine = None
1067 cnt_mine = 0
1068 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001069 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001070 if committer_email == self.UserEmail:
1071 last_mine = commit_id
1072 cnt_mine += 1
1073
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001074 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001075 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076
1077 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001078 syncbuf.fail(self, _DirtyError())
1079 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001081 # If the upstream switched on us, warn the user.
1082 #
1083 if branch.merge != self.revisionExpr:
1084 if branch.merge and self.revisionExpr:
1085 syncbuf.info(self,
1086 'manifest switched %s...%s',
1087 branch.merge,
1088 self.revisionExpr)
1089 elif branch.merge:
1090 syncbuf.info(self,
1091 'manifest no longer tracks %s',
1092 branch.merge)
1093
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001094 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001096 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001098 syncbuf.info(self,
1099 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001100 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001102 branch.remote = self.GetRemote(self.remote.name)
1103 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104 branch.Save()
1105
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001106 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001108 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001109 self._CopyFiles()
1110 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001111 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 self._CopyFiles()
1115 except GitError, e:
1116 syncbuf.fail(self, e)
1117 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001119 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001120 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 self._CopyFiles()
1122 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001124 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125 # dest should already be an absolute path, but src is project relative
1126 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001127 abssrc = os.path.join(self.worktree, src)
1128 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001130 def DownloadPatchSet(self, change_id, patch_id):
1131 """Download a single patch set of a single change to FETCH_HEAD.
1132 """
1133 remote = self.GetRemote(self.remote.name)
1134
1135 cmd = ['fetch', remote.name]
1136 cmd.append('refs/changes/%2.2d/%d/%d' \
1137 % (change_id % 100, change_id, patch_id))
1138 cmd.extend(map(lambda x: str(x), remote.fetch))
1139 if GitCommand(self, cmd, bare=True).Wait() != 0:
1140 return None
1141 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001142 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001143 change_id,
1144 patch_id,
1145 self.bare_git.rev_parse('FETCH_HEAD'))
1146
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147
1148## Branch Management ##
1149
1150 def StartBranch(self, name):
1151 """Create a new branch off the manifest's revision.
1152 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001153 head = self.work_git.GetHead()
1154 if head == (R_HEADS + name):
1155 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001157 all = self.bare_ref.all
1158 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001159 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001160 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001161 capture_stdout = True,
1162 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001163
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001164 branch = self.GetBranch(name)
1165 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001166 branch.merge = self.revisionExpr
1167 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001168
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001169 if head.startswith(R_HEADS):
1170 try:
1171 head = all[head]
1172 except KeyError:
1173 head = None
1174
1175 if revid and head and revid == head:
1176 ref = os.path.join(self.gitdir, R_HEADS + name)
1177 try:
1178 os.makedirs(os.path.dirname(ref))
1179 except OSError:
1180 pass
1181 _lwrite(ref, '%s\n' % revid)
1182 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1183 'ref: %s%s\n' % (R_HEADS, name))
1184 branch.Save()
1185 return True
1186
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001187 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001188 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001189 capture_stdout = True,
1190 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001191 branch.Save()
1192 return True
1193 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194
Wink Saville02d79452009-04-10 13:01:24 -07001195 def CheckoutBranch(self, name):
1196 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001197
1198 Args:
1199 name: The name of the branch to checkout.
1200
1201 Returns:
1202 True if the checkout succeeded; False if it didn't; None if the branch
1203 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001204 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001205 rev = R_HEADS + name
1206 head = self.work_git.GetHead()
1207 if head == rev:
1208 # Already on the branch
1209 #
1210 return True
Wink Saville02d79452009-04-10 13:01:24 -07001211
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001212 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001213 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001214 revid = all[rev]
1215 except KeyError:
1216 # Branch does not exist in this project
1217 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001218 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001219
1220 if head.startswith(R_HEADS):
1221 try:
1222 head = all[head]
1223 except KeyError:
1224 head = None
1225
1226 if head == revid:
1227 # Same revision; just update HEAD to point to the new
1228 # target branch, but otherwise take no other action.
1229 #
1230 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1231 'ref: %s%s\n' % (R_HEADS, name))
1232 return True
Wink Saville02d79452009-04-10 13:01:24 -07001233
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001234 return GitCommand(self,
1235 ['checkout', name, '--'],
1236 capture_stdout = True,
1237 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001238
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001239 def AbandonBranch(self, name):
1240 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001241
1242 Args:
1243 name: The name of the branch to abandon.
1244
1245 Returns:
1246 True if the abandon succeeded; False if it didn't; None if the branch
1247 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001248 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001249 rev = R_HEADS + name
1250 all = self.bare_ref.all
1251 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001252 # Doesn't exist
1253 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001254
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001255 head = self.work_git.GetHead()
1256 if head == rev:
1257 # We can't destroy the branch while we are sitting
1258 # on it. Switch to a detached HEAD.
1259 #
1260 head = all[head]
1261
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001262 revid = self.GetRevisionId(all)
1263 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001264 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1265 '%s\n' % revid)
1266 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001267 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001268
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001269 return GitCommand(self,
1270 ['branch', '-D', name],
1271 capture_stdout = True,
1272 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001273
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 def PruneHeads(self):
1275 """Prune any topic branches already merged into upstream.
1276 """
1277 cb = self.CurrentBranch
1278 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001279 left = self._allrefs
1280 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 if name.startswith(R_HEADS):
1282 name = name[len(R_HEADS):]
1283 if cb is None or name != cb:
1284 kill.append(name)
1285
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001286 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 if cb is not None \
1288 and not self._revlist(HEAD + '...' + rev) \
1289 and not self.IsDirty(consider_untracked = False):
1290 self.work_git.DetachHead(HEAD)
1291 kill.append(cb)
1292
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001294 old = self.bare_git.GetHead()
1295 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1297
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 try:
1299 self.bare_git.DetachHead(rev)
1300
1301 b = ['branch', '-d']
1302 b.extend(kill)
1303 b = GitCommand(self, b, bare=True,
1304 capture_stdout=True,
1305 capture_stderr=True)
1306 b.Wait()
1307 finally:
1308 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001309 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001311 for branch in kill:
1312 if (R_HEADS + branch) not in left:
1313 self.CleanPublishedCache()
1314 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
1316 if cb and cb not in kill:
1317 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001318 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319
1320 kept = []
1321 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001322 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 branch = self.GetBranch(branch)
1324 base = branch.LocalMerge
1325 if not base:
1326 base = rev
1327 kept.append(ReviewableBranch(self, branch, base))
1328 return kept
1329
1330
1331## Direct Git Commands ##
1332
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001333 def _RemoteFetch(self, name=None, tag=None,
1334 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001335 quiet=False,
1336 alt_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 if not name:
1338 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001339
1340 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001341 remote = self.GetRemote(name)
1342 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001343 ssh_proxy = True
1344
Shawn O. Pearce88443382010-10-08 10:02:09 +02001345 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001346 if alt_dir and 'objects' == os.path.basename(alt_dir):
1347 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001348 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1349 remote = self.GetRemote(name)
1350
1351 all = self.bare_ref.all
1352 ids = set(all.values())
1353 tmp = set()
1354
1355 for r, id in GitRefs(ref_dir).all.iteritems():
1356 if r not in all:
1357 if r.startswith(R_TAGS) or remote.WritesTo(r):
1358 all[r] = id
1359 ids.add(id)
1360 continue
1361
1362 if id in ids:
1363 continue
1364
1365 r = 'refs/_alt/%s' % id
1366 all[r] = id
1367 ids.add(id)
1368 tmp.add(r)
1369
1370 ref_names = list(all.keys())
1371 ref_names.sort()
1372
1373 tmp_packed = ''
1374 old_packed = ''
1375
1376 for r in ref_names:
1377 line = '%s %s\n' % (all[r], r)
1378 tmp_packed += line
1379 if r not in tmp:
1380 old_packed += line
1381
1382 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001383 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001384 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001385
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001386 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001387
1388 # The --depth option only affects the initial fetch; after that we'll do
1389 # full fetches of changes.
1390 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1391 if depth and initial:
1392 cmd.append('--depth=%s' % depth)
1393
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001394 if quiet:
1395 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001396 if not self.worktree:
1397 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001398 cmd.append(name)
1399 if tag is not None:
1400 cmd.append('tag')
1401 cmd.append(tag)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001402
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001403 ok = False
1404 for i in range(2):
1405 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1406 ok = True
1407 break
1408 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001409
1410 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001411 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001412 if old_packed != '':
1413 _lwrite(packed_refs, old_packed)
1414 else:
1415 os.remove(packed_refs)
1416 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001417 return ok
1418
1419 def _ApplyCloneBundle(self, initial=False, quiet=False):
1420 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1421 return False
1422
1423 remote = self.GetRemote(self.remote.name)
1424 bundle_url = remote.url + '/clone.bundle'
1425 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1426 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1427 return False
1428
1429 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1430 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001431
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001432 exist_dst = os.path.exists(bundle_dst)
1433 exist_tmp = os.path.exists(bundle_tmp)
1434
1435 if not initial and not exist_dst and not exist_tmp:
1436 return False
1437
1438 if not exist_dst:
1439 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1440 if not exist_dst:
1441 return False
1442
1443 cmd = ['fetch']
1444 if quiet:
1445 cmd.append('--quiet')
1446 if not self.worktree:
1447 cmd.append('--update-head-ok')
1448 cmd.append(bundle_dst)
1449 for f in remote.fetch:
1450 cmd.append(str(f))
1451 cmd.append('refs/tags/*:refs/tags/*')
1452
1453 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001454 if os.path.exists(bundle_dst):
1455 os.remove(bundle_dst)
1456 if os.path.exists(bundle_tmp):
1457 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001458 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001460 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001461 keep = True
1462 done = False
1463 dest = open(tmpPath, 'a+b')
1464 try:
1465 dest.seek(0, os.SEEK_END)
1466 pos = dest.tell()
1467
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001468 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001469 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001470 req = urllib2.Request(srcUrl)
1471 if pos > 0:
1472 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001473
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001474 try:
1475 r = urllib2.urlopen(req)
1476 except urllib2.HTTPError, e:
1477 def _content_type():
1478 try:
1479 return e.info()['content-type']
1480 except:
1481 return None
1482
1483 if e.code == 404:
1484 keep = False
1485 return False
1486 elif _content_type() == 'text/plain':
1487 try:
1488 msg = e.read()
1489 if len(msg) > 0 and msg[-1] == '\n':
1490 msg = msg[0:-1]
1491 msg = ' (%s)' % msg
1492 except:
1493 msg = ''
1494 else:
1495 try:
1496 from BaseHTTPServer import BaseHTTPRequestHandler
1497 res = BaseHTTPRequestHandler.responses[e.code]
1498 msg = ' (%s: %s)' % (res[0], res[1])
1499 except:
1500 msg = ''
1501 raise DownloadError('HTTP %s%s' % (e.code, msg))
1502 except urllib2.URLError, e:
1503 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1504 finally:
1505 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001506
1507 p = None
1508 try:
1509 size = r.headers['content-length']
1510 unit = 1 << 10
1511
1512 if size and not quiet:
1513 if size > 1024 * 1.3:
1514 unit = 1 << 20
1515 desc = 'MB'
1516 else:
1517 desc = 'KB'
1518 p = Progress(
1519 'Downloading %s' % self.relpath,
1520 int(size) / unit,
1521 units=desc)
1522 if pos > 0:
1523 p.update(pos / unit)
1524
1525 s = 0
1526 while True:
1527 d = r.read(8192)
1528 if d == '':
1529 done = True
1530 return True
1531 dest.write(d)
1532 if p:
1533 s += len(d)
1534 if s >= unit:
1535 p.update(s / unit)
1536 s = s % unit
1537 if p:
1538 if s >= unit:
1539 p.update(s / unit)
1540 else:
1541 p.update(1)
1542 finally:
1543 r.close()
1544 if p:
1545 p.end()
1546 finally:
1547 dest.close()
1548
1549 if os.path.exists(dstPath):
1550 os.remove(dstPath)
1551 if done:
1552 os.rename(tmpPath, dstPath)
1553 elif not keep:
1554 os.remove(tmpPath)
1555
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556 def _Checkout(self, rev, quiet=False):
1557 cmd = ['checkout']
1558 if quiet:
1559 cmd.append('-q')
1560 cmd.append(rev)
1561 cmd.append('--')
1562 if GitCommand(self, cmd).Wait() != 0:
1563 if self._allrefs:
1564 raise GitError('%s checkout %s ' % (self.name, rev))
1565
1566 def _ResetHard(self, rev, quiet=True):
1567 cmd = ['reset', '--hard']
1568 if quiet:
1569 cmd.append('-q')
1570 cmd.append(rev)
1571 if GitCommand(self, cmd).Wait() != 0:
1572 raise GitError('%s reset --hard %s ' % (self.name, rev))
1573
1574 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001575 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001576 if onto is not None:
1577 cmd.extend(['--onto', onto])
1578 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001579 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001580 raise GitError('%s rebase %s ' % (self.name, upstream))
1581
1582 def _FastForward(self, head):
1583 cmd = ['merge', head]
1584 if GitCommand(self, cmd).Wait() != 0:
1585 raise GitError('%s merge %s ' % (self.name, head))
1586
1587 def _InitGitDir(self):
1588 if not os.path.exists(self.gitdir):
1589 os.makedirs(self.gitdir)
1590 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001591
Shawn O. Pearce88443382010-10-08 10:02:09 +02001592 mp = self.manifest.manifestProject
1593 ref_dir = mp.config.GetString('repo.reference')
1594
1595 if ref_dir:
1596 mirror_git = os.path.join(ref_dir, self.name + '.git')
1597 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1598 self.relpath + '.git')
1599
1600 if os.path.exists(mirror_git):
1601 ref_dir = mirror_git
1602
1603 elif os.path.exists(repo_git):
1604 ref_dir = repo_git
1605
1606 else:
1607 ref_dir = None
1608
1609 if ref_dir:
1610 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1611 os.path.join(ref_dir, 'objects') + '\n')
1612
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001613 if self.manifest.IsMirror:
1614 self.config.SetString('core.bare', 'true')
1615 else:
1616 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001617
1618 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001619 try:
1620 to_rm = os.listdir(hooks)
1621 except OSError:
1622 to_rm = []
1623 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001624 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001625 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001626
1627 m = self.manifest.manifestProject.config
1628 for key in ['user.name', 'user.email']:
1629 if m.Has(key, include_defaults = False):
1630 self.config.SetString(key, m.GetString(key))
1631
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001632 def _InitHooks(self):
1633 hooks = self._gitdir_path('hooks')
1634 if not os.path.exists(hooks):
1635 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001636 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001637 name = os.path.basename(stock_hook)
1638
Victor Boivie65e0f352011-04-18 11:23:29 +02001639 if name in ('commit-msg',) and not self.remote.review \
1640 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001641 # Don't install a Gerrit Code Review hook if this
1642 # project does not appear to use it for reviews.
1643 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001644 # Since the manifest project is one of those, but also
1645 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001646 continue
1647
1648 dst = os.path.join(hooks, name)
1649 if os.path.islink(dst):
1650 continue
1651 if os.path.exists(dst):
1652 if filecmp.cmp(stock_hook, dst, shallow=False):
1653 os.remove(dst)
1654 else:
1655 _error("%s: Not replacing %s hook", self.relpath, name)
1656 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001657 try:
1658 os.symlink(relpath(stock_hook, dst), dst)
1659 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001660 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001661 raise GitError('filesystem must support symlinks')
1662 else:
1663 raise
1664
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001666 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001668 remote.url = self.remote.url
1669 remote.review = self.remote.review
1670 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001672 if self.worktree:
1673 remote.ResetFetch(mirror=False)
1674 else:
1675 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676 remote.Save()
1677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 def _InitMRef(self):
1679 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001680 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001682 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001683 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001684
1685 def _InitAnyMRef(self, ref):
1686 cur = self.bare_ref.symref(ref)
1687
1688 if self.revisionId:
1689 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1690 msg = 'manifest set to %s' % self.revisionId
1691 dst = self.revisionId + '^0'
1692 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1693 else:
1694 remote = self.GetRemote(self.remote.name)
1695 dst = remote.ToLocal(self.revisionExpr)
1696 if cur != dst:
1697 msg = 'manifest set to %s' % self.revisionExpr
1698 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700 def _InitWorkTree(self):
1701 dotgit = os.path.join(self.worktree, '.git')
1702 if not os.path.exists(dotgit):
1703 os.makedirs(dotgit)
1704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 for name in ['config',
1706 'description',
1707 'hooks',
1708 'info',
1709 'logs',
1710 'objects',
1711 'packed-refs',
1712 'refs',
1713 'rr-cache',
1714 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001715 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001716 src = os.path.join(self.gitdir, name)
1717 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001718 if os.path.islink(dst) or not os.path.exists(dst):
1719 os.symlink(relpath(src, dst), dst)
1720 else:
1721 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001722 except OSError, e:
1723 if e.errno == errno.EPERM:
1724 raise GitError('filesystem must support symlinks')
1725 else:
1726 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001728 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729
1730 cmd = ['read-tree', '--reset', '-u']
1731 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001732 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733 if GitCommand(self, cmd).Wait() != 0:
1734 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001735
1736 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1737 if not os.path.exists(rr_cache):
1738 os.makedirs(rr_cache)
1739
Shawn O. Pearce93609662009-04-21 10:50:33 -07001740 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741
1742 def _gitdir_path(self, path):
1743 return os.path.join(self.gitdir, path)
1744
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001745 def _revlist(self, *args, **kw):
1746 a = []
1747 a.extend(args)
1748 a.append('--')
1749 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750
1751 @property
1752 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001753 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754
1755 class _GitGetByExec(object):
1756 def __init__(self, project, bare):
1757 self._project = project
1758 self._bare = bare
1759
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 def LsOthers(self):
1761 p = GitCommand(self._project,
1762 ['ls-files',
1763 '-z',
1764 '--others',
1765 '--exclude-standard'],
1766 bare = False,
1767 capture_stdout = True,
1768 capture_stderr = True)
1769 if p.Wait() == 0:
1770 out = p.stdout
1771 if out:
1772 return out[:-1].split("\0")
1773 return []
1774
1775 def DiffZ(self, name, *args):
1776 cmd = [name]
1777 cmd.append('-z')
1778 cmd.extend(args)
1779 p = GitCommand(self._project,
1780 cmd,
1781 bare = False,
1782 capture_stdout = True,
1783 capture_stderr = True)
1784 try:
1785 out = p.process.stdout.read()
1786 r = {}
1787 if out:
1788 out = iter(out[:-1].split('\0'))
1789 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001790 try:
1791 info = out.next()
1792 path = out.next()
1793 except StopIteration:
1794 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001795
1796 class _Info(object):
1797 def __init__(self, path, omode, nmode, oid, nid, state):
1798 self.path = path
1799 self.src_path = None
1800 self.old_mode = omode
1801 self.new_mode = nmode
1802 self.old_id = oid
1803 self.new_id = nid
1804
1805 if len(state) == 1:
1806 self.status = state
1807 self.level = None
1808 else:
1809 self.status = state[:1]
1810 self.level = state[1:]
1811 while self.level.startswith('0'):
1812 self.level = self.level[1:]
1813
1814 info = info[1:].split(' ')
1815 info =_Info(path, *info)
1816 if info.status in ('R', 'C'):
1817 info.src_path = info.path
1818 info.path = out.next()
1819 r[info.path] = info
1820 return r
1821 finally:
1822 p.Wait()
1823
1824 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001825 if self._bare:
1826 path = os.path.join(self._project.gitdir, HEAD)
1827 else:
1828 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001829 fd = open(path, 'rb')
1830 try:
1831 line = fd.read()
1832 finally:
1833 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001834 if line.startswith('ref: '):
1835 return line[5:-1]
1836 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001837
1838 def SetHead(self, ref, message=None):
1839 cmdv = []
1840 if message is not None:
1841 cmdv.extend(['-m', message])
1842 cmdv.append(HEAD)
1843 cmdv.append(ref)
1844 self.symbolic_ref(*cmdv)
1845
1846 def DetachHead(self, new, message=None):
1847 cmdv = ['--no-deref']
1848 if message is not None:
1849 cmdv.extend(['-m', message])
1850 cmdv.append(HEAD)
1851 cmdv.append(new)
1852 self.update_ref(*cmdv)
1853
1854 def UpdateRef(self, name, new, old=None,
1855 message=None,
1856 detach=False):
1857 cmdv = []
1858 if message is not None:
1859 cmdv.extend(['-m', message])
1860 if detach:
1861 cmdv.append('--no-deref')
1862 cmdv.append(name)
1863 cmdv.append(new)
1864 if old is not None:
1865 cmdv.append(old)
1866 self.update_ref(*cmdv)
1867
1868 def DeleteRef(self, name, old=None):
1869 if not old:
1870 old = self.rev_parse(name)
1871 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001872 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001873
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001874 def rev_list(self, *args, **kw):
1875 if 'format' in kw:
1876 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1877 else:
1878 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001879 cmdv.extend(args)
1880 p = GitCommand(self._project,
1881 cmdv,
1882 bare = self._bare,
1883 capture_stdout = True,
1884 capture_stderr = True)
1885 r = []
1886 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001887 if line[-1] == '\n':
1888 line = line[:-1]
1889 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890 if p.Wait() != 0:
1891 raise GitError('%s rev-list %s: %s' % (
1892 self._project.name,
1893 str(args),
1894 p.stderr))
1895 return r
1896
1897 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001898 """Allow arbitrary git commands using pythonic syntax.
1899
1900 This allows you to do things like:
1901 git_obj.rev_parse('HEAD')
1902
1903 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1904 run. We'll replace the '_' with a '-' and try to run a git command.
1905 Any other arguments will be passed to the git command.
1906
1907 Args:
1908 name: The name of the git command to call. Any '_' characters will
1909 be replaced with '-'.
1910
1911 Returns:
1912 A callable object that will try to call git with the named command.
1913 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001914 name = name.replace('_', '-')
1915 def runner(*args):
1916 cmdv = [name]
1917 cmdv.extend(args)
1918 p = GitCommand(self._project,
1919 cmdv,
1920 bare = self._bare,
1921 capture_stdout = True,
1922 capture_stderr = True)
1923 if p.Wait() != 0:
1924 raise GitError('%s %s: %s' % (
1925 self._project.name,
1926 name,
1927 p.stderr))
1928 r = p.stdout
1929 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1930 return r[:-1]
1931 return r
1932 return runner
1933
1934
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001935class _PriorSyncFailedError(Exception):
1936 def __str__(self):
1937 return 'prior sync failed; rebase still in progress'
1938
1939class _DirtyError(Exception):
1940 def __str__(self):
1941 return 'contains uncommitted changes'
1942
1943class _InfoMessage(object):
1944 def __init__(self, project, text):
1945 self.project = project
1946 self.text = text
1947
1948 def Print(self, syncbuf):
1949 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1950 syncbuf.out.nl()
1951
1952class _Failure(object):
1953 def __init__(self, project, why):
1954 self.project = project
1955 self.why = why
1956
1957 def Print(self, syncbuf):
1958 syncbuf.out.fail('error: %s/: %s',
1959 self.project.relpath,
1960 str(self.why))
1961 syncbuf.out.nl()
1962
1963class _Later(object):
1964 def __init__(self, project, action):
1965 self.project = project
1966 self.action = action
1967
1968 def Run(self, syncbuf):
1969 out = syncbuf.out
1970 out.project('project %s/', self.project.relpath)
1971 out.nl()
1972 try:
1973 self.action()
1974 out.nl()
1975 return True
1976 except GitError, e:
1977 out.nl()
1978 return False
1979
1980class _SyncColoring(Coloring):
1981 def __init__(self, config):
1982 Coloring.__init__(self, config, 'reposync')
1983 self.project = self.printer('header', attr = 'bold')
1984 self.info = self.printer('info')
1985 self.fail = self.printer('fail', fg='red')
1986
1987class SyncBuffer(object):
1988 def __init__(self, config, detach_head=False):
1989 self._messages = []
1990 self._failures = []
1991 self._later_queue1 = []
1992 self._later_queue2 = []
1993
1994 self.out = _SyncColoring(config)
1995 self.out.redirect(sys.stderr)
1996
1997 self.detach_head = detach_head
1998 self.clean = True
1999
2000 def info(self, project, fmt, *args):
2001 self._messages.append(_InfoMessage(project, fmt % args))
2002
2003 def fail(self, project, err=None):
2004 self._failures.append(_Failure(project, err))
2005 self.clean = False
2006
2007 def later1(self, project, what):
2008 self._later_queue1.append(_Later(project, what))
2009
2010 def later2(self, project, what):
2011 self._later_queue2.append(_Later(project, what))
2012
2013 def Finish(self):
2014 self._PrintMessages()
2015 self._RunLater()
2016 self._PrintMessages()
2017 return self.clean
2018
2019 def _RunLater(self):
2020 for q in ['_later_queue1', '_later_queue2']:
2021 if not self._RunQueue(q):
2022 return
2023
2024 def _RunQueue(self, queue):
2025 for m in getattr(self, queue):
2026 if not m.Run(self):
2027 self.clean = False
2028 return False
2029 setattr(self, queue, [])
2030 return True
2031
2032 def _PrintMessages(self):
2033 for m in self._messages:
2034 m.Print(self)
2035 for m in self._failures:
2036 m.Print(self)
2037
2038 self._messages = []
2039 self._failures = []
2040
2041
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042class MetaProject(Project):
2043 """A special project housed under .repo.
2044 """
2045 def __init__(self, manifest, name, gitdir, worktree):
2046 repodir = manifest.repodir
2047 Project.__init__(self,
2048 manifest = manifest,
2049 name = name,
2050 gitdir = gitdir,
2051 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002052 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002053 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002054 revisionExpr = 'refs/heads/master',
2055 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002056
2057 def PreSync(self):
2058 if self.Exists:
2059 cb = self.CurrentBranch
2060 if cb:
2061 base = self.GetBranch(cb).merge
2062 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002063 self.revisionExpr = base
2064 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002065
2066 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002067 def LastFetch(self):
2068 try:
2069 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2070 return os.path.getmtime(fh)
2071 except OSError:
2072 return 0
2073
2074 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002075 def HasChanges(self):
2076 """Has the remote received new commits not yet checked out?
2077 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002078 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002079 return False
2080
2081 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002082 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002083 head = self.work_git.GetHead()
2084 if head.startswith(R_HEADS):
2085 try:
2086 head = all[head]
2087 except KeyError:
2088 head = None
2089
2090 if revid == head:
2091 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002092 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093 return True
2094 return False