blob: 5adfe82ead1cc27df2cdc1385a33e8300a3222e3 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Anderson37282b42011-03-04 11:54:18 -080015import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
18import os
19import re
20import shutil
21import stat
22import sys
23import urllib2
24
25from color import Coloring
26from git_command import GitCommand
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070027from git_config import GitConfig, IsId, GetSchemeFromUrl
28from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080029from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080030from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070031from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
Shawn O. Pearced237b692009-04-17 18:49:50 -070033from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070035def _lwrite(path, content):
36 lock = '%s.lock' % path
37
38 fd = open(lock, 'wb')
39 try:
40 fd.write(content)
41 finally:
42 fd.close()
43
44 try:
45 os.rename(lock, path)
46 except OSError:
47 os.remove(lock)
48 raise
49
Shawn O. Pearce48244782009-04-16 08:25:57 -070050def _error(fmt, *args):
51 msg = fmt % args
52 print >>sys.stderr, 'error: %s' % msg
53
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070054def not_rev(r):
55 return '^' + r
56
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080057def sq(r):
58 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080059
Doug Anderson8ced8642011-01-10 14:16:30 -080060_project_hook_list = None
61def _ProjectHooks():
62 """List the hooks present in the 'hooks' directory.
63
64 These hooks are project hooks and are copied to the '.git/hooks' directory
65 of all subprojects.
66
67 This function caches the list of hooks (based on the contents of the
68 'repo/hooks' directory) on the first call.
69
70 Returns:
71 A list of absolute paths to all of the files in the hooks directory.
72 """
73 global _project_hook_list
74 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080075 d = os.path.abspath(os.path.dirname(__file__))
76 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080077 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
78 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080079
80def relpath(dst, src):
81 src = os.path.dirname(src)
82 top = os.path.commonprefix([dst, src])
83 if top.endswith('/'):
84 top = top[:-1]
85 else:
86 top = os.path.dirname(top)
87
88 tmp = src
89 rel = ''
90 while top != tmp:
91 rel += '../'
92 tmp = os.path.dirname(tmp)
93 return rel + dst[len(top) + 1:]
94
95
Shawn O. Pearce632768b2008-10-23 11:58:52 -070096class DownloadedChange(object):
97 _commit_cache = None
98
99 def __init__(self, project, base, change_id, ps_id, commit):
100 self.project = project
101 self.base = base
102 self.change_id = change_id
103 self.ps_id = ps_id
104 self.commit = commit
105
106 @property
107 def commits(self):
108 if self._commit_cache is None:
109 self._commit_cache = self.project.bare_git.rev_list(
110 '--abbrev=8',
111 '--abbrev-commit',
112 '--pretty=oneline',
113 '--reverse',
114 '--date-order',
115 not_rev(self.base),
116 self.commit,
117 '--')
118 return self._commit_cache
119
120
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121class ReviewableBranch(object):
122 _commit_cache = None
123
124 def __init__(self, project, branch, base):
125 self.project = project
126 self.branch = branch
127 self.base = base
128
129 @property
130 def name(self):
131 return self.branch.name
132
133 @property
134 def commits(self):
135 if self._commit_cache is None:
136 self._commit_cache = self.project.bare_git.rev_list(
137 '--abbrev=8',
138 '--abbrev-commit',
139 '--pretty=oneline',
140 '--reverse',
141 '--date-order',
142 not_rev(self.base),
143 R_HEADS + self.name,
144 '--')
145 return self._commit_cache
146
147 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800148 def unabbrev_commits(self):
149 r = dict()
150 for commit in self.project.bare_git.rev_list(
151 not_rev(self.base),
152 R_HEADS + self.name,
153 '--'):
154 r[commit[0:8]] = commit
155 return r
156
157 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 def date(self):
159 return self.project.bare_git.log(
160 '--pretty=format:%cd',
161 '-n', '1',
162 R_HEADS + self.name,
163 '--')
164
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700165 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700167 people,
168 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700170 def GetPublishedRefs(self):
171 refs = {}
172 output = self.project.bare_git.ls_remote(
173 self.branch.remote.SshReviewUrl(self.project.UserEmail),
174 'refs/changes/*')
175 for line in output.split('\n'):
176 try:
177 (sha, ref) = line.split()
178 refs[sha] = ref
179 except ValueError:
180 pass
181
182 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
184class StatusColoring(Coloring):
185 def __init__(self, config):
186 Coloring.__init__(self, config, 'status')
187 self.project = self.printer('header', attr = 'bold')
188 self.branch = self.printer('header', attr = 'bold')
189 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700190 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191
192 self.added = self.printer('added', fg = 'green')
193 self.changed = self.printer('changed', fg = 'red')
194 self.untracked = self.printer('untracked', fg = 'red')
195
196
197class DiffColoring(Coloring):
198 def __init__(self, config):
199 Coloring.__init__(self, config, 'diff')
200 self.project = self.printer('header', attr = 'bold')
201
202
203class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800204 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 self.src = src
206 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 self.abs_src = abssrc
208 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
210 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800211 src = self.abs_src
212 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 # copy file if it does not exist or is out of date
214 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
215 try:
216 # remove existing file first, since it might be read-only
217 if os.path.exists(dest):
218 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400219 else:
220 dir = os.path.dirname(dest)
221 if not os.path.isdir(dir):
222 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223 shutil.copy(src, dest)
224 # make the file read-only
225 mode = os.stat(dest)[stat.ST_MODE]
226 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
227 os.chmod(dest, mode)
228 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700229 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700231class RemoteSpec(object):
232 def __init__(self,
233 name,
234 url = None,
235 review = None):
236 self.name = name
237 self.url = url
238 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Doug Anderson37282b42011-03-04 11:54:18 -0800240class RepoHook(object):
241 """A RepoHook contains information about a script to run as a hook.
242
243 Hooks are used to run a python script before running an upload (for instance,
244 to run presubmit checks). Eventually, we may have hooks for other actions.
245
246 This shouldn't be confused with files in the 'repo/hooks' directory. Those
247 files are copied into each '.git/hooks' folder for each project. Repo-level
248 hooks are associated instead with repo actions.
249
250 Hooks are always python. When a hook is run, we will load the hook into the
251 interpreter and execute its main() function.
252 """
253 def __init__(self,
254 hook_type,
255 hooks_project,
256 topdir,
257 abort_if_user_denies=False):
258 """RepoHook constructor.
259
260 Params:
261 hook_type: A string representing the type of hook. This is also used
262 to figure out the name of the file containing the hook. For
263 example: 'pre-upload'.
264 hooks_project: The project containing the repo hooks. If you have a
265 manifest, this is manifest.repo_hooks_project. OK if this is None,
266 which will make the hook a no-op.
267 topdir: Repo's top directory (the one containing the .repo directory).
268 Scripts will run with CWD as this directory. If you have a manifest,
269 this is manifest.topdir
270 abort_if_user_denies: If True, we'll throw a HookError() if the user
271 doesn't allow us to run the hook.
272 """
273 self._hook_type = hook_type
274 self._hooks_project = hooks_project
275 self._topdir = topdir
276 self._abort_if_user_denies = abort_if_user_denies
277
278 # Store the full path to the script for convenience.
279 if self._hooks_project:
280 self._script_fullpath = os.path.join(self._hooks_project.worktree,
281 self._hook_type + '.py')
282 else:
283 self._script_fullpath = None
284
285 def _GetHash(self):
286 """Return a hash of the contents of the hooks directory.
287
288 We'll just use git to do this. This hash has the property that if anything
289 changes in the directory we will return a different has.
290
291 SECURITY CONSIDERATION:
292 This hash only represents the contents of files in the hook directory, not
293 any other files imported or called by hooks. Changes to imported files
294 can change the script behavior without affecting the hash.
295
296 Returns:
297 A string representing the hash. This will always be ASCII so that it can
298 be printed to the user easily.
299 """
300 assert self._hooks_project, "Must have hooks to calculate their hash."
301
302 # We will use the work_git object rather than just calling GetRevisionId().
303 # That gives us a hash of the latest checked in version of the files that
304 # the user will actually be executing. Specifically, GetRevisionId()
305 # doesn't appear to change even if a user checks out a different version
306 # of the hooks repo (via git checkout) nor if a user commits their own revs.
307 #
308 # NOTE: Local (non-committed) changes will not be factored into this hash.
309 # I think this is OK, since we're really only worried about warning the user
310 # about upstream changes.
311 return self._hooks_project.work_git.rev_parse('HEAD')
312
313 def _GetMustVerb(self):
314 """Return 'must' if the hook is required; 'should' if not."""
315 if self._abort_if_user_denies:
316 return 'must'
317 else:
318 return 'should'
319
320 def _CheckForHookApproval(self):
321 """Check to see whether this hook has been approved.
322
323 We'll look at the hash of all of the hooks. If this matches the hash that
324 the user last approved, we're done. If it doesn't, we'll ask the user
325 about approval.
326
327 Note that we ask permission for each individual hook even though we use
328 the hash of all hooks when detecting changes. We'd like the user to be
329 able to approve / deny each hook individually. We only use the hash of all
330 hooks because there is no other easy way to detect changes to local imports.
331
332 Returns:
333 True if this hook is approved to run; False otherwise.
334
335 Raises:
336 HookError: Raised if the user doesn't approve and abort_if_user_denies
337 was passed to the consturctor.
338 """
339 hooks_dir = self._hooks_project.worktree
340 hooks_config = self._hooks_project.config
341 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
342
343 # Get the last hash that the user approved for this hook; may be None.
344 old_hash = hooks_config.GetString(git_approval_key)
345
346 # Get the current hash so we can tell if scripts changed since approval.
347 new_hash = self._GetHash()
348
349 if old_hash is not None:
350 # User previously approved hook and asked not to be prompted again.
351 if new_hash == old_hash:
352 # Approval matched. We're done.
353 return True
354 else:
355 # Give the user a reason why we're prompting, since they last told
356 # us to "never ask again".
357 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
358 self._hook_type)
359 else:
360 prompt = ''
361
362 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
363 if sys.stdout.isatty():
364 prompt += ('Repo %s run the script:\n'
365 ' %s\n'
366 '\n'
367 'Do you want to allow this script to run '
368 '(yes/yes-never-ask-again/NO)? ') % (
369 self._GetMustVerb(), self._script_fullpath)
370 response = raw_input(prompt).lower()
371 print
372
373 # User is doing a one-time approval.
374 if response in ('y', 'yes'):
375 return True
376 elif response == 'yes-never-ask-again':
377 hooks_config.SetString(git_approval_key, new_hash)
378 return True
379
380 # For anything else, we'll assume no approval.
381 if self._abort_if_user_denies:
382 raise HookError('You must allow the %s hook or use --no-verify.' %
383 self._hook_type)
384
385 return False
386
387 def _ExecuteHook(self, **kwargs):
388 """Actually execute the given hook.
389
390 This will run the hook's 'main' function in our python interpreter.
391
392 Args:
393 kwargs: Keyword arguments to pass to the hook. These are often specific
394 to the hook type. For instance, pre-upload hooks will contain
395 a project_list.
396 """
397 # Keep sys.path and CWD stashed away so that we can always restore them
398 # upon function exit.
399 orig_path = os.getcwd()
400 orig_syspath = sys.path
401
402 try:
403 # Always run hooks with CWD as topdir.
404 os.chdir(self._topdir)
405
406 # Put the hook dir as the first item of sys.path so hooks can do
407 # relative imports. We want to replace the repo dir as [0] so
408 # hooks can't import repo files.
409 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
410
411 # Exec, storing global context in the context dict. We catch exceptions
412 # and convert to a HookError w/ just the failing traceback.
413 context = {}
414 try:
415 execfile(self._script_fullpath, context)
416 except Exception:
417 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
418 traceback.format_exc(), self._hook_type))
419
420 # Running the script should have defined a main() function.
421 if 'main' not in context:
422 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
423
424
425 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
426 # We don't actually want hooks to define their main with this argument--
427 # it's there to remind them that their hook should always take **kwargs.
428 # For instance, a pre-upload hook should be defined like:
429 # def main(project_list, **kwargs):
430 #
431 # This allows us to later expand the API without breaking old hooks.
432 kwargs = kwargs.copy()
433 kwargs['hook_should_take_kwargs'] = True
434
435 # Call the main function in the hook. If the hook should cause the
436 # build to fail, it will raise an Exception. We'll catch that convert
437 # to a HookError w/ just the failing traceback.
438 try:
439 context['main'](**kwargs)
440 except Exception:
441 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
442 'above.' % (
443 traceback.format_exc(), self._hook_type))
444 finally:
445 # Restore sys.path and CWD.
446 sys.path = orig_syspath
447 os.chdir(orig_path)
448
449 def Run(self, user_allows_all_hooks, **kwargs):
450 """Run the hook.
451
452 If the hook doesn't exist (because there is no hooks project or because
453 this particular hook is not enabled), this is a no-op.
454
455 Args:
456 user_allows_all_hooks: If True, we will never prompt about running the
457 hook--we'll just assume it's OK to run it.
458 kwargs: Keyword arguments to pass to the hook. These are often specific
459 to the hook type. For instance, pre-upload hooks will contain
460 a project_list.
461
462 Raises:
463 HookError: If there was a problem finding the hook or the user declined
464 to run a required hook (from _CheckForHookApproval).
465 """
466 # No-op if there is no hooks project or if hook is disabled.
467 if ((not self._hooks_project) or
468 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
469 return
470
471 # Bail with a nice error if we can't find the hook.
472 if not os.path.isfile(self._script_fullpath):
473 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
474
475 # Make sure the user is OK with running the hook.
476 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
477 return
478
479 # Run the hook with the same version of python we're using.
480 self._ExecuteHook(**kwargs)
481
482
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700483class Project(object):
484 def __init__(self,
485 manifest,
486 name,
487 remote,
488 gitdir,
489 worktree,
490 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700491 revisionExpr,
492 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700493 self.manifest = manifest
494 self.name = name
495 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800496 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800497 if worktree:
498 self.worktree = worktree.replace('\\', '/')
499 else:
500 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700501 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700502 self.revisionExpr = revisionExpr
503
504 if revisionId is None \
505 and revisionExpr \
506 and IsId(revisionExpr):
507 self.revisionId = revisionExpr
508 else:
509 self.revisionId = revisionId
510
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700511 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.copyfiles = []
513 self.config = GitConfig.ForRepository(
514 gitdir = self.gitdir,
515 defaults = self.manifest.globalConfig)
516
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800517 if self.worktree:
518 self.work_git = self._GitGetByExec(self, bare=False)
519 else:
520 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700521 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700522 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523
Doug Anderson37282b42011-03-04 11:54:18 -0800524 # This will be filled in if a project is later identified to be the
525 # project containing repo hooks.
526 self.enabled_repo_hooks = []
527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528 @property
529 def Exists(self):
530 return os.path.isdir(self.gitdir)
531
532 @property
533 def CurrentBranch(self):
534 """Obtain the name of the currently checked out branch.
535 The branch name omits the 'refs/heads/' prefix.
536 None is returned if the project is on a detached HEAD.
537 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700538 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 if b.startswith(R_HEADS):
540 return b[len(R_HEADS):]
541 return None
542
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700543 def IsRebaseInProgress(self):
544 w = self.worktree
545 g = os.path.join(w, '.git')
546 return os.path.exists(os.path.join(g, 'rebase-apply')) \
547 or os.path.exists(os.path.join(g, 'rebase-merge')) \
548 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200549
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550 def IsDirty(self, consider_untracked=True):
551 """Is the working directory modified in some way?
552 """
553 self.work_git.update_index('-q',
554 '--unmerged',
555 '--ignore-missing',
556 '--refresh')
557 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
558 return True
559 if self.work_git.DiffZ('diff-files'):
560 return True
561 if consider_untracked and self.work_git.LsOthers():
562 return True
563 return False
564
565 _userident_name = None
566 _userident_email = None
567
568 @property
569 def UserName(self):
570 """Obtain the user's personal name.
571 """
572 if self._userident_name is None:
573 self._LoadUserIdentity()
574 return self._userident_name
575
576 @property
577 def UserEmail(self):
578 """Obtain the user's email address. This is very likely
579 to be their Gerrit login.
580 """
581 if self._userident_email is None:
582 self._LoadUserIdentity()
583 return self._userident_email
584
585 def _LoadUserIdentity(self):
586 u = self.bare_git.var('GIT_COMMITTER_IDENT')
587 m = re.compile("^(.*) <([^>]*)> ").match(u)
588 if m:
589 self._userident_name = m.group(1)
590 self._userident_email = m.group(2)
591 else:
592 self._userident_name = ''
593 self._userident_email = ''
594
595 def GetRemote(self, name):
596 """Get the configuration for a single remote.
597 """
598 return self.config.GetRemote(name)
599
600 def GetBranch(self, name):
601 """Get the configuration for a single branch.
602 """
603 return self.config.GetBranch(name)
604
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700605 def GetBranches(self):
606 """Get all existing local branches.
607 """
608 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700609 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700610 heads = {}
611 pubd = {}
612
613 for name, id in all.iteritems():
614 if name.startswith(R_HEADS):
615 name = name[len(R_HEADS):]
616 b = self.GetBranch(name)
617 b.current = name == current
618 b.published = None
619 b.revision = id
620 heads[name] = b
621
622 for name, id in all.iteritems():
623 if name.startswith(R_PUB):
624 name = name[len(R_PUB):]
625 b = heads.get(name)
626 if b:
627 b.published = id
628
629 return heads
630
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631
632## Status Display ##
633
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500634 def HasChanges(self):
635 """Returns true if there are uncommitted changes.
636 """
637 self.work_git.update_index('-q',
638 '--unmerged',
639 '--ignore-missing',
640 '--refresh')
641 if self.IsRebaseInProgress():
642 return True
643
644 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
645 return True
646
647 if self.work_git.DiffZ('diff-files'):
648 return True
649
650 if self.work_git.LsOthers():
651 return True
652
653 return False
654
Terence Haddock4655e812011-03-31 12:33:34 +0200655 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200657
658 Args:
659 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700660 """
661 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200662 if output_redir == None:
663 output_redir = sys.stdout
664 print >>output_redir, ''
665 print >>output_redir, 'project %s/' % self.relpath
666 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 return
668
669 self.work_git.update_index('-q',
670 '--unmerged',
671 '--ignore-missing',
672 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700673 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
675 df = self.work_git.DiffZ('diff-files')
676 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700677 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700678 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679
680 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200681 if not output_redir == None:
682 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 out.project('project %-40s', self.relpath + '/')
684
685 branch = self.CurrentBranch
686 if branch is None:
687 out.nobranch('(*** NO BRANCH ***)')
688 else:
689 out.branch('branch %s', branch)
690 out.nl()
691
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700692 if rb:
693 out.important('prior sync failed; rebase still in progress')
694 out.nl()
695
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 paths = list()
697 paths.extend(di.keys())
698 paths.extend(df.keys())
699 paths.extend(do)
700
701 paths = list(set(paths))
702 paths.sort()
703
704 for p in paths:
705 try: i = di[p]
706 except KeyError: i = None
707
708 try: f = df[p]
709 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200710
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700711 if i: i_status = i.status.upper()
712 else: i_status = '-'
713
714 if f: f_status = f.status.lower()
715 else: f_status = '-'
716
717 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800718 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 i.src_path, p, i.level)
720 else:
721 line = ' %s%s\t%s' % (i_status, f_status, p)
722
723 if i and not f:
724 out.added('%s', line)
725 elif (i and f) or (not i and f):
726 out.changed('%s', line)
727 elif not i and not f:
728 out.untracked('%s', line)
729 else:
730 out.write('%s', line)
731 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200732
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700733 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734
735 def PrintWorkTreeDiff(self):
736 """Prints the status of the repository to stdout.
737 """
738 out = DiffColoring(self.config)
739 cmd = ['diff']
740 if out.is_on:
741 cmd.append('--color')
742 cmd.append(HEAD)
743 cmd.append('--')
744 p = GitCommand(self,
745 cmd,
746 capture_stdout = True,
747 capture_stderr = True)
748 has_diff = False
749 for line in p.process.stdout:
750 if not has_diff:
751 out.nl()
752 out.project('project %s/' % self.relpath)
753 out.nl()
754 has_diff = True
755 print line[:-1]
756 p.Wait()
757
758
759## Publish / Upload ##
760
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700761 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 """Was the branch published (uploaded) for code review?
763 If so, returns the SHA-1 hash of the last published
764 state for the branch.
765 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700766 key = R_PUB + branch
767 if all is None:
768 try:
769 return self.bare_git.rev_parse(key)
770 except GitError:
771 return None
772 else:
773 try:
774 return all[key]
775 except KeyError:
776 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700778 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779 """Prunes any stale published refs.
780 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700781 if all is None:
782 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 heads = set()
784 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700785 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 if name.startswith(R_HEADS):
787 heads.add(name)
788 elif name.startswith(R_PUB):
789 canrm[name] = id
790
791 for name, id in canrm.iteritems():
792 n = name[len(R_PUB):]
793 if R_HEADS + n not in heads:
794 self.bare_git.DeleteRef(name, id)
795
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700796 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 """List any branches which can be uploaded for review.
798 """
799 heads = {}
800 pubed = {}
801
802 for name, id in self._allrefs.iteritems():
803 if name.startswith(R_HEADS):
804 heads[name[len(R_HEADS):]] = id
805 elif name.startswith(R_PUB):
806 pubed[name[len(R_PUB):]] = id
807
808 ready = []
809 for branch, id in heads.iteritems():
810 if branch in pubed and pubed[branch] == id:
811 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700812 if selected_branch and branch != selected_branch:
813 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800815 rb = self.GetUploadableBranch(branch)
816 if rb:
817 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 return ready
819
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800820 def GetUploadableBranch(self, branch_name):
821 """Get a single uploadable branch, or None.
822 """
823 branch = self.GetBranch(branch_name)
824 base = branch.LocalMerge
825 if branch.LocalMerge:
826 rb = ReviewableBranch(self, branch, base)
827 if rb.commits:
828 return rb
829 return None
830
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700831 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700832 people=([],[]),
833 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 """Uploads the named branch for code review.
835 """
836 if branch is None:
837 branch = self.CurrentBranch
838 if branch is None:
839 raise GitError('not currently on a branch')
840
841 branch = self.GetBranch(branch)
842 if not branch.LocalMerge:
843 raise GitError('branch %s does not track a remote' % branch.name)
844 if not branch.remote.review:
845 raise GitError('remote %s has no review url' % branch.remote.name)
846
847 dest_branch = branch.merge
848 if not dest_branch.startswith(R_HEADS):
849 dest_branch = R_HEADS + dest_branch
850
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800851 if not branch.remote.projectname:
852 branch.remote.projectname = self.name
853 branch.remote.Save()
854
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800855 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800856 if dest_branch.startswith(R_HEADS):
857 dest_branch = dest_branch[len(R_HEADS):]
858
859 rp = ['gerrit receive-pack']
860 for e in people[0]:
861 rp.append('--reviewer=%s' % sq(e))
862 for e in people[1]:
863 rp.append('--cc=%s' % sq(e))
864
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700865 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
866 if auto_topic:
867 ref_spec = ref_spec + '/' + branch.name
868
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800869 cmd = ['push']
870 cmd.append('--receive-pack=%s' % " ".join(rp))
871 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700872 cmd.append(ref_spec)
873
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800874 if GitCommand(self, cmd, bare = True).Wait() != 0:
875 raise UploadError('Upload failed')
876
877 else:
878 raise UploadError('Unsupported protocol %s' \
879 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880
881 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
882 self.bare_git.UpdateRef(R_PUB + branch.name,
883 R_HEADS + branch.name,
884 message = msg)
885
886
887## Sync ##
888
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700889 def Sync_NetworkHalf(self, quiet=False, is_new=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 """Perform only the network IO portion of the sync process.
891 Local working directory/branch state is not affected.
892 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700893 if is_new is None:
894 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200895 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800897
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 self._InitRemote()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700899 if not self._RemoteFetch(initial=is_new, quiet=quiet):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800901
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200902 #Check that the requested ref was found after fetch
903 #
904 try:
905 self.GetRevisionId()
906 except ManifestInvalidRevisionError:
907 # if the ref is a tag. We can try fetching
908 # the tag manually as a last resort
909 #
910 rev = self.revisionExpr
911 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700912 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200913
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800914 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800915 self._InitMRef()
916 else:
917 self._InitMirrorHead()
918 try:
919 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
920 except OSError:
921 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800923
924 def PostRepoUpgrade(self):
925 self._InitHooks()
926
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 def _CopyFiles(self):
928 for file in self.copyfiles:
929 file._Copy()
930
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700931 def GetRevisionId(self, all=None):
932 if self.revisionId:
933 return self.revisionId
934
935 rem = self.GetRemote(self.remote.name)
936 rev = rem.ToLocal(self.revisionExpr)
937
938 if all is not None and rev in all:
939 return all[rev]
940
941 try:
942 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
943 except GitError:
944 raise ManifestInvalidRevisionError(
945 'revision %s in %s not found' % (self.revisionExpr,
946 self.name))
947
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700948 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 """Perform only the local IO portion of the sync process.
950 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700952 all = self.bare_ref.all
953 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700954 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800955
956 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700957 head = self.work_git.GetHead()
958 if head.startswith(R_HEADS):
959 branch = head[len(R_HEADS):]
960 try:
961 head = all[head]
962 except KeyError:
963 head = None
964 else:
965 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700967 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 # Currently on a detached HEAD. The user is assumed to
969 # not have any local modifications worth worrying about.
970 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700971 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700972 syncbuf.fail(self, _PriorSyncFailedError())
973 return
974
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700975 if head == revid:
976 # No changes; don't do anything further.
977 #
978 return
979
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700980 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700982 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700984 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700985 except GitError, e:
986 syncbuf.fail(self, e)
987 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700989 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700991 if head == revid:
992 # No changes; don't do anything further.
993 #
994 return
995
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700998 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 # The current branch has no tracking configuration.
1000 # Jump off it to a deatched HEAD.
1001 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001002 syncbuf.info(self,
1003 "leaving %s; does not track upstream",
1004 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001006 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001007 except GitError, e:
1008 syncbuf.fail(self, e)
1009 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001011 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001013 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001014 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001016 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 if not_merged:
1018 if upstream_gain:
1019 # The user has published this branch and some of those
1020 # commits are not yet merged upstream. We do not want
1021 # to rewrite the published commits so we punt.
1022 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001023 syncbuf.fail(self,
1024 "branch %s is published (but not merged) and is now %d commits behind"
1025 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001026 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001027 elif pub == head:
1028 # All published commits are merged, and thus we are a
1029 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001030 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001031 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001032 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001033 self._CopyFiles()
1034 syncbuf.later1(self, _doff)
1035 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001037 # Examine the local commits not in the remote. Find the
1038 # last one attributed to this user, if any.
1039 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001041 last_mine = None
1042 cnt_mine = 0
1043 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001044 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001045 if committer_email == self.UserEmail:
1046 last_mine = commit_id
1047 cnt_mine += 1
1048
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001049 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001050 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051
1052 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 syncbuf.fail(self, _DirtyError())
1054 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001056 # If the upstream switched on us, warn the user.
1057 #
1058 if branch.merge != self.revisionExpr:
1059 if branch.merge and self.revisionExpr:
1060 syncbuf.info(self,
1061 'manifest switched %s...%s',
1062 branch.merge,
1063 self.revisionExpr)
1064 elif branch.merge:
1065 syncbuf.info(self,
1066 'manifest no longer tracks %s',
1067 branch.merge)
1068
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001069 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001071 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001073 syncbuf.info(self,
1074 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001075 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001077 branch.remote = self.GetRemote(self.remote.name)
1078 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 branch.Save()
1080
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001081 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 self._CopyFiles()
1085 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001086 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001088 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001089 self._CopyFiles()
1090 except GitError, e:
1091 syncbuf.fail(self, e)
1092 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001094 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001095 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001096 self._CopyFiles()
1097 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001099 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 # dest should already be an absolute path, but src is project relative
1101 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001102 abssrc = os.path.join(self.worktree, src)
1103 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001105 def DownloadPatchSet(self, change_id, patch_id):
1106 """Download a single patch set of a single change to FETCH_HEAD.
1107 """
1108 remote = self.GetRemote(self.remote.name)
1109
1110 cmd = ['fetch', remote.name]
1111 cmd.append('refs/changes/%2.2d/%d/%d' \
1112 % (change_id % 100, change_id, patch_id))
1113 cmd.extend(map(lambda x: str(x), remote.fetch))
1114 if GitCommand(self, cmd, bare=True).Wait() != 0:
1115 return None
1116 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001117 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001118 change_id,
1119 patch_id,
1120 self.bare_git.rev_parse('FETCH_HEAD'))
1121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122
1123## Branch Management ##
1124
1125 def StartBranch(self, name):
1126 """Create a new branch off the manifest's revision.
1127 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001128 head = self.work_git.GetHead()
1129 if head == (R_HEADS + name):
1130 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001132 all = self.bare_ref.all
1133 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001134 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001135 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001136 capture_stdout = True,
1137 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001138
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001139 branch = self.GetBranch(name)
1140 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001141 branch.merge = self.revisionExpr
1142 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001143
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001144 if head.startswith(R_HEADS):
1145 try:
1146 head = all[head]
1147 except KeyError:
1148 head = None
1149
1150 if revid and head and revid == head:
1151 ref = os.path.join(self.gitdir, R_HEADS + name)
1152 try:
1153 os.makedirs(os.path.dirname(ref))
1154 except OSError:
1155 pass
1156 _lwrite(ref, '%s\n' % revid)
1157 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1158 'ref: %s%s\n' % (R_HEADS, name))
1159 branch.Save()
1160 return True
1161
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001162 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001164 capture_stdout = True,
1165 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001166 branch.Save()
1167 return True
1168 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169
Wink Saville02d79452009-04-10 13:01:24 -07001170 def CheckoutBranch(self, name):
1171 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001172
1173 Args:
1174 name: The name of the branch to checkout.
1175
1176 Returns:
1177 True if the checkout succeeded; False if it didn't; None if the branch
1178 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001179 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001180 rev = R_HEADS + name
1181 head = self.work_git.GetHead()
1182 if head == rev:
1183 # Already on the branch
1184 #
1185 return True
Wink Saville02d79452009-04-10 13:01:24 -07001186
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001187 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001188 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001189 revid = all[rev]
1190 except KeyError:
1191 # Branch does not exist in this project
1192 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001193 return None
Wink Saville02d79452009-04-10 13:01:24 -07001194
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001195 if head.startswith(R_HEADS):
1196 try:
1197 head = all[head]
1198 except KeyError:
1199 head = None
1200
1201 if head == revid:
1202 # Same revision; just update HEAD to point to the new
1203 # target branch, but otherwise take no other action.
1204 #
1205 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1206 'ref: %s%s\n' % (R_HEADS, name))
1207 return True
1208
1209 return GitCommand(self,
1210 ['checkout', name, '--'],
1211 capture_stdout = True,
1212 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001213
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001214 def AbandonBranch(self, name):
1215 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001216
1217 Args:
1218 name: The name of the branch to abandon.
1219
1220 Returns:
1221 True if the abandon succeeded; False if it didn't; None if the branch
1222 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001223 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001224 rev = R_HEADS + name
1225 all = self.bare_ref.all
1226 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001227 # Doesn't exist
1228 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001229
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001230 head = self.work_git.GetHead()
1231 if head == rev:
1232 # We can't destroy the branch while we are sitting
1233 # on it. Switch to a detached HEAD.
1234 #
1235 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001236
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001237 revid = self.GetRevisionId(all)
1238 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001239 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1240 '%s\n' % revid)
1241 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001242 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001243
1244 return GitCommand(self,
1245 ['branch', '-D', name],
1246 capture_stdout = True,
1247 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001248
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249 def PruneHeads(self):
1250 """Prune any topic branches already merged into upstream.
1251 """
1252 cb = self.CurrentBranch
1253 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001254 left = self._allrefs
1255 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 if name.startswith(R_HEADS):
1257 name = name[len(R_HEADS):]
1258 if cb is None or name != cb:
1259 kill.append(name)
1260
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001261 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 if cb is not None \
1263 and not self._revlist(HEAD + '...' + rev) \
1264 and not self.IsDirty(consider_untracked = False):
1265 self.work_git.DetachHead(HEAD)
1266 kill.append(cb)
1267
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001269 old = self.bare_git.GetHead()
1270 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1272
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273 try:
1274 self.bare_git.DetachHead(rev)
1275
1276 b = ['branch', '-d']
1277 b.extend(kill)
1278 b = GitCommand(self, b, bare=True,
1279 capture_stdout=True,
1280 capture_stderr=True)
1281 b.Wait()
1282 finally:
1283 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001284 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001286 for branch in kill:
1287 if (R_HEADS + branch) not in left:
1288 self.CleanPublishedCache()
1289 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290
1291 if cb and cb not in kill:
1292 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001293 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294
1295 kept = []
1296 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001297 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 branch = self.GetBranch(branch)
1299 base = branch.LocalMerge
1300 if not base:
1301 base = rev
1302 kept.append(ReviewableBranch(self, branch, base))
1303 return kept
1304
1305
1306## Direct Git Commands ##
1307
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001308 def _RemoteFetch(self, name=None, tag=None,
1309 initial=False,
1310 quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 if not name:
1312 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001313
1314 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001315 remote = self.GetRemote(name)
1316 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001317 ssh_proxy = True
1318
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001319 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1320 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1321 use_bundle = False
1322 if os.path.exists(bundle_dst) or os.path.exists(bundle_tmp):
1323 use_bundle = True
1324
Shawn O. Pearce88443382010-10-08 10:02:09 +02001325 if initial:
1326 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1327 try:
1328 fd = open(alt, 'rb')
1329 try:
1330 ref_dir = fd.readline()
1331 if ref_dir and ref_dir.endswith('\n'):
1332 ref_dir = ref_dir[:-1]
1333 finally:
1334 fd.close()
1335 except IOError, e:
1336 ref_dir = None
1337
1338 if ref_dir and 'objects' == os.path.basename(ref_dir):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001339 if use_bundle:
1340 use_bundle = False
Shawn O. Pearce88443382010-10-08 10:02:09 +02001341 ref_dir = os.path.dirname(ref_dir)
1342 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1343 remote = self.GetRemote(name)
1344
1345 all = self.bare_ref.all
1346 ids = set(all.values())
1347 tmp = set()
1348
1349 for r, id in GitRefs(ref_dir).all.iteritems():
1350 if r not in all:
1351 if r.startswith(R_TAGS) or remote.WritesTo(r):
1352 all[r] = id
1353 ids.add(id)
1354 continue
1355
1356 if id in ids:
1357 continue
1358
1359 r = 'refs/_alt/%s' % id
1360 all[r] = id
1361 ids.add(id)
1362 tmp.add(r)
1363
1364 ref_names = list(all.keys())
1365 ref_names.sort()
1366
1367 tmp_packed = ''
1368 old_packed = ''
1369
1370 for r in ref_names:
1371 line = '%s %s\n' % (all[r], r)
1372 tmp_packed += line
1373 if r not in tmp:
1374 old_packed += line
1375
1376 _lwrite(packed_refs, tmp_packed)
1377
1378 else:
1379 ref_dir = None
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001380 use_bundle = True
Shawn O. Pearce88443382010-10-08 10:02:09 +02001381
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001382 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001383
1384 # The --depth option only affects the initial fetch; after that we'll do
1385 # full fetches of changes.
1386 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1387 if depth and initial:
1388 cmd.append('--depth=%s' % depth)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001389 use_bundle = False
Doug Anderson30d45292011-05-04 15:01:04 -07001390
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001391 if quiet:
1392 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001393 if not self.worktree:
1394 cmd.append('--update-head-ok')
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001395
1396 if use_bundle and not os.path.exists(bundle_dst):
1397 bundle_url = remote.url + '/clone.bundle'
1398 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1399 if GetSchemeFromUrl(bundle_url) in ('http', 'https'):
1400 use_bundle = self._FetchBundle(
1401 bundle_url,
1402 bundle_tmp,
1403 bundle_dst,
1404 quiet=quiet)
1405 else:
1406 use_bundle = False
1407
1408 if use_bundle:
1409 if not quiet:
1410 cmd.append('--quiet')
1411 cmd.append(bundle_dst)
1412 for f in remote.fetch:
1413 cmd.append(str(f))
1414 cmd.append('refs/tags/*:refs/tags/*')
1415 else:
1416 cmd.append(name)
1417 if tag is not None:
1418 cmd.append('tag')
1419 cmd.append(tag)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001420
1421 ok = GitCommand(self,
1422 cmd,
1423 bare = True,
1424 ssh_proxy = ssh_proxy).Wait() == 0
1425
1426 if initial:
1427 if ref_dir:
1428 if old_packed != '':
1429 _lwrite(packed_refs, old_packed)
1430 else:
1431 os.remove(packed_refs)
1432 self.bare_git.pack_refs('--all', '--prune')
1433
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001434 if os.path.exists(bundle_dst):
1435 os.remove(bundle_dst)
1436 if os.path.exists(bundle_tmp):
1437 os.remove(bundle_tmp)
1438
Shawn O. Pearce88443382010-10-08 10:02:09 +02001439 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001440
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001441 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet=False):
1442 keep = True
1443 done = False
1444 dest = open(tmpPath, 'a+b')
1445 try:
1446 dest.seek(0, os.SEEK_END)
1447 pos = dest.tell()
1448
1449 req = urllib2.Request(srcUrl)
1450 if pos > 0:
1451 req.add_header('Range', 'bytes=%d-' % pos)
1452
1453 try:
1454 r = urllib2.urlopen(req)
1455 except urllib2.HTTPError, e:
1456 if e.code == 404:
1457 keep = False
1458 return False
1459 elif e.info()['content-type'] == 'text/plain':
1460 try:
1461 msg = e.read()
1462 if len(msg) > 0 and msg[-1] == '\n':
1463 msg = msg[0:-1]
1464 msg = ' (%s)' % msg
1465 except:
1466 msg = ''
1467 else:
1468 try:
1469 from BaseHTTPServer import BaseHTTPRequestHandler
1470 res = BaseHTTPRequestHandler.responses[e.code]
1471 msg = ' (%s: %s)' % (res[0], res[1])
1472 except:
1473 msg = ''
1474 raise DownloadError('HTTP %s%s' % (e.code, msg))
1475 except urllib2.URLError, e:
1476 raise DownloadError('%s (%s)' % (e.reason, req.get_host()))
1477
1478 p = None
1479 try:
1480 size = r.headers['content-length']
1481 unit = 1 << 10
1482
1483 if size and not quiet:
1484 if size > 1024 * 1.3:
1485 unit = 1 << 20
1486 desc = 'MB'
1487 else:
1488 desc = 'KB'
1489 p = Progress(
1490 'Downloading %s' % self.relpath,
1491 int(size) / unit,
1492 units=desc)
1493 if pos > 0:
1494 p.update(pos / unit)
1495
1496 s = 0
1497 while True:
1498 d = r.read(8192)
1499 if d == '':
1500 done = True
1501 return True
1502 dest.write(d)
1503 if p:
1504 s += len(d)
1505 if s >= unit:
1506 p.update(s / unit)
1507 s = s % unit
1508 if p:
1509 if s >= unit:
1510 p.update(s / unit)
1511 else:
1512 p.update(1)
1513 finally:
1514 r.close()
1515 if p:
1516 p.end()
1517 finally:
1518 dest.close()
1519
1520 if os.path.exists(dstPath):
1521 os.remove(dstPath)
1522 if done:
1523 os.rename(tmpPath, dstPath)
1524 elif not keep:
1525 os.remove(tmpPath)
1526
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527 def _Checkout(self, rev, quiet=False):
1528 cmd = ['checkout']
1529 if quiet:
1530 cmd.append('-q')
1531 cmd.append(rev)
1532 cmd.append('--')
1533 if GitCommand(self, cmd).Wait() != 0:
1534 if self._allrefs:
1535 raise GitError('%s checkout %s ' % (self.name, rev))
1536
1537 def _ResetHard(self, rev, quiet=True):
1538 cmd = ['reset', '--hard']
1539 if quiet:
1540 cmd.append('-q')
1541 cmd.append(rev)
1542 if GitCommand(self, cmd).Wait() != 0:
1543 raise GitError('%s reset --hard %s ' % (self.name, rev))
1544
1545 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001546 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001547 if onto is not None:
1548 cmd.extend(['--onto', onto])
1549 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001550 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551 raise GitError('%s rebase %s ' % (self.name, upstream))
1552
1553 def _FastForward(self, head):
1554 cmd = ['merge', head]
1555 if GitCommand(self, cmd).Wait() != 0:
1556 raise GitError('%s merge %s ' % (self.name, head))
1557
1558 def _InitGitDir(self):
1559 if not os.path.exists(self.gitdir):
1560 os.makedirs(self.gitdir)
1561 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001562
Shawn O. Pearce88443382010-10-08 10:02:09 +02001563 mp = self.manifest.manifestProject
1564 ref_dir = mp.config.GetString('repo.reference')
1565
1566 if ref_dir:
1567 mirror_git = os.path.join(ref_dir, self.name + '.git')
1568 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1569 self.relpath + '.git')
1570
1571 if os.path.exists(mirror_git):
1572 ref_dir = mirror_git
1573
1574 elif os.path.exists(repo_git):
1575 ref_dir = repo_git
1576
1577 else:
1578 ref_dir = None
1579
1580 if ref_dir:
1581 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1582 os.path.join(ref_dir, 'objects') + '\n')
1583
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001584 if self.manifest.IsMirror:
1585 self.config.SetString('core.bare', 'true')
1586 else:
1587 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001588
1589 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001590 try:
1591 to_rm = os.listdir(hooks)
1592 except OSError:
1593 to_rm = []
1594 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001595 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001596 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597
1598 m = self.manifest.manifestProject.config
1599 for key in ['user.name', 'user.email']:
1600 if m.Has(key, include_defaults = False):
1601 self.config.SetString(key, m.GetString(key))
1602
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001603 def _InitHooks(self):
1604 hooks = self._gitdir_path('hooks')
1605 if not os.path.exists(hooks):
1606 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001607 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001608 name = os.path.basename(stock_hook)
1609
Victor Boivie65e0f352011-04-18 11:23:29 +02001610 if name in ('commit-msg',) and not self.remote.review \
1611 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001612 # Don't install a Gerrit Code Review hook if this
1613 # project does not appear to use it for reviews.
1614 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001615 # Since the manifest project is one of those, but also
1616 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001617 continue
1618
1619 dst = os.path.join(hooks, name)
1620 if os.path.islink(dst):
1621 continue
1622 if os.path.exists(dst):
1623 if filecmp.cmp(stock_hook, dst, shallow=False):
1624 os.remove(dst)
1625 else:
1626 _error("%s: Not replacing %s hook", self.relpath, name)
1627 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001628 try:
1629 os.symlink(relpath(stock_hook, dst), dst)
1630 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001631 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001632 raise GitError('filesystem must support symlinks')
1633 else:
1634 raise
1635
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001636 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001637 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001638 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001639 remote.url = self.remote.url
1640 remote.review = self.remote.review
1641 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001642
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001643 if self.worktree:
1644 remote.ResetFetch(mirror=False)
1645 else:
1646 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001647 remote.Save()
1648
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649 def _InitMRef(self):
1650 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001651 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001653 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001654 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001655
1656 def _InitAnyMRef(self, ref):
1657 cur = self.bare_ref.symref(ref)
1658
1659 if self.revisionId:
1660 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1661 msg = 'manifest set to %s' % self.revisionId
1662 dst = self.revisionId + '^0'
1663 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1664 else:
1665 remote = self.GetRemote(self.remote.name)
1666 dst = remote.ToLocal(self.revisionExpr)
1667 if cur != dst:
1668 msg = 'manifest set to %s' % self.revisionExpr
1669 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001670
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671 def _InitWorkTree(self):
1672 dotgit = os.path.join(self.worktree, '.git')
1673 if not os.path.exists(dotgit):
1674 os.makedirs(dotgit)
1675
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676 for name in ['config',
1677 'description',
1678 'hooks',
1679 'info',
1680 'logs',
1681 'objects',
1682 'packed-refs',
1683 'refs',
1684 'rr-cache',
1685 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001686 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001687 src = os.path.join(self.gitdir, name)
1688 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001689 if os.path.islink(dst) or not os.path.exists(dst):
1690 os.symlink(relpath(src, dst), dst)
1691 else:
1692 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001693 except OSError, e:
1694 if e.errno == errno.EPERM:
1695 raise GitError('filesystem must support symlinks')
1696 else:
1697 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001699 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700
1701 cmd = ['read-tree', '--reset', '-u']
1702 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001703 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 if GitCommand(self, cmd).Wait() != 0:
1705 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001706
1707 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1708 if not os.path.exists(rr_cache):
1709 os.makedirs(rr_cache)
1710
Shawn O. Pearce93609662009-04-21 10:50:33 -07001711 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712
1713 def _gitdir_path(self, path):
1714 return os.path.join(self.gitdir, path)
1715
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001716 def _revlist(self, *args, **kw):
1717 a = []
1718 a.extend(args)
1719 a.append('--')
1720 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001721
1722 @property
1723 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001724 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725
1726 class _GitGetByExec(object):
1727 def __init__(self, project, bare):
1728 self._project = project
1729 self._bare = bare
1730
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731 def LsOthers(self):
1732 p = GitCommand(self._project,
1733 ['ls-files',
1734 '-z',
1735 '--others',
1736 '--exclude-standard'],
1737 bare = False,
1738 capture_stdout = True,
1739 capture_stderr = True)
1740 if p.Wait() == 0:
1741 out = p.stdout
1742 if out:
1743 return out[:-1].split("\0")
1744 return []
1745
1746 def DiffZ(self, name, *args):
1747 cmd = [name]
1748 cmd.append('-z')
1749 cmd.extend(args)
1750 p = GitCommand(self._project,
1751 cmd,
1752 bare = False,
1753 capture_stdout = True,
1754 capture_stderr = True)
1755 try:
1756 out = p.process.stdout.read()
1757 r = {}
1758 if out:
1759 out = iter(out[:-1].split('\0'))
1760 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001761 try:
1762 info = out.next()
1763 path = out.next()
1764 except StopIteration:
1765 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766
1767 class _Info(object):
1768 def __init__(self, path, omode, nmode, oid, nid, state):
1769 self.path = path
1770 self.src_path = None
1771 self.old_mode = omode
1772 self.new_mode = nmode
1773 self.old_id = oid
1774 self.new_id = nid
1775
1776 if len(state) == 1:
1777 self.status = state
1778 self.level = None
1779 else:
1780 self.status = state[:1]
1781 self.level = state[1:]
1782 while self.level.startswith('0'):
1783 self.level = self.level[1:]
1784
1785 info = info[1:].split(' ')
1786 info =_Info(path, *info)
1787 if info.status in ('R', 'C'):
1788 info.src_path = info.path
1789 info.path = out.next()
1790 r[info.path] = info
1791 return r
1792 finally:
1793 p.Wait()
1794
1795 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001796 if self._bare:
1797 path = os.path.join(self._project.gitdir, HEAD)
1798 else:
1799 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001800 fd = open(path, 'rb')
1801 try:
1802 line = fd.read()
1803 finally:
1804 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001805 if line.startswith('ref: '):
1806 return line[5:-1]
1807 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808
1809 def SetHead(self, ref, message=None):
1810 cmdv = []
1811 if message is not None:
1812 cmdv.extend(['-m', message])
1813 cmdv.append(HEAD)
1814 cmdv.append(ref)
1815 self.symbolic_ref(*cmdv)
1816
1817 def DetachHead(self, new, message=None):
1818 cmdv = ['--no-deref']
1819 if message is not None:
1820 cmdv.extend(['-m', message])
1821 cmdv.append(HEAD)
1822 cmdv.append(new)
1823 self.update_ref(*cmdv)
1824
1825 def UpdateRef(self, name, new, old=None,
1826 message=None,
1827 detach=False):
1828 cmdv = []
1829 if message is not None:
1830 cmdv.extend(['-m', message])
1831 if detach:
1832 cmdv.append('--no-deref')
1833 cmdv.append(name)
1834 cmdv.append(new)
1835 if old is not None:
1836 cmdv.append(old)
1837 self.update_ref(*cmdv)
1838
1839 def DeleteRef(self, name, old=None):
1840 if not old:
1841 old = self.rev_parse(name)
1842 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001843 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001844
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001845 def rev_list(self, *args, **kw):
1846 if 'format' in kw:
1847 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1848 else:
1849 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001850 cmdv.extend(args)
1851 p = GitCommand(self._project,
1852 cmdv,
1853 bare = self._bare,
1854 capture_stdout = True,
1855 capture_stderr = True)
1856 r = []
1857 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001858 if line[-1] == '\n':
1859 line = line[:-1]
1860 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861 if p.Wait() != 0:
1862 raise GitError('%s rev-list %s: %s' % (
1863 self._project.name,
1864 str(args),
1865 p.stderr))
1866 return r
1867
1868 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001869 """Allow arbitrary git commands using pythonic syntax.
1870
1871 This allows you to do things like:
1872 git_obj.rev_parse('HEAD')
1873
1874 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1875 run. We'll replace the '_' with a '-' and try to run a git command.
1876 Any other arguments will be passed to the git command.
1877
1878 Args:
1879 name: The name of the git command to call. Any '_' characters will
1880 be replaced with '-'.
1881
1882 Returns:
1883 A callable object that will try to call git with the named command.
1884 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001885 name = name.replace('_', '-')
1886 def runner(*args):
1887 cmdv = [name]
1888 cmdv.extend(args)
1889 p = GitCommand(self._project,
1890 cmdv,
1891 bare = self._bare,
1892 capture_stdout = True,
1893 capture_stderr = True)
1894 if p.Wait() != 0:
1895 raise GitError('%s %s: %s' % (
1896 self._project.name,
1897 name,
1898 p.stderr))
1899 r = p.stdout
1900 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1901 return r[:-1]
1902 return r
1903 return runner
1904
1905
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001906class _PriorSyncFailedError(Exception):
1907 def __str__(self):
1908 return 'prior sync failed; rebase still in progress'
1909
1910class _DirtyError(Exception):
1911 def __str__(self):
1912 return 'contains uncommitted changes'
1913
1914class _InfoMessage(object):
1915 def __init__(self, project, text):
1916 self.project = project
1917 self.text = text
1918
1919 def Print(self, syncbuf):
1920 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1921 syncbuf.out.nl()
1922
1923class _Failure(object):
1924 def __init__(self, project, why):
1925 self.project = project
1926 self.why = why
1927
1928 def Print(self, syncbuf):
1929 syncbuf.out.fail('error: %s/: %s',
1930 self.project.relpath,
1931 str(self.why))
1932 syncbuf.out.nl()
1933
1934class _Later(object):
1935 def __init__(self, project, action):
1936 self.project = project
1937 self.action = action
1938
1939 def Run(self, syncbuf):
1940 out = syncbuf.out
1941 out.project('project %s/', self.project.relpath)
1942 out.nl()
1943 try:
1944 self.action()
1945 out.nl()
1946 return True
1947 except GitError, e:
1948 out.nl()
1949 return False
1950
1951class _SyncColoring(Coloring):
1952 def __init__(self, config):
1953 Coloring.__init__(self, config, 'reposync')
1954 self.project = self.printer('header', attr = 'bold')
1955 self.info = self.printer('info')
1956 self.fail = self.printer('fail', fg='red')
1957
1958class SyncBuffer(object):
1959 def __init__(self, config, detach_head=False):
1960 self._messages = []
1961 self._failures = []
1962 self._later_queue1 = []
1963 self._later_queue2 = []
1964
1965 self.out = _SyncColoring(config)
1966 self.out.redirect(sys.stderr)
1967
1968 self.detach_head = detach_head
1969 self.clean = True
1970
1971 def info(self, project, fmt, *args):
1972 self._messages.append(_InfoMessage(project, fmt % args))
1973
1974 def fail(self, project, err=None):
1975 self._failures.append(_Failure(project, err))
1976 self.clean = False
1977
1978 def later1(self, project, what):
1979 self._later_queue1.append(_Later(project, what))
1980
1981 def later2(self, project, what):
1982 self._later_queue2.append(_Later(project, what))
1983
1984 def Finish(self):
1985 self._PrintMessages()
1986 self._RunLater()
1987 self._PrintMessages()
1988 return self.clean
1989
1990 def _RunLater(self):
1991 for q in ['_later_queue1', '_later_queue2']:
1992 if not self._RunQueue(q):
1993 return
1994
1995 def _RunQueue(self, queue):
1996 for m in getattr(self, queue):
1997 if not m.Run(self):
1998 self.clean = False
1999 return False
2000 setattr(self, queue, [])
2001 return True
2002
2003 def _PrintMessages(self):
2004 for m in self._messages:
2005 m.Print(self)
2006 for m in self._failures:
2007 m.Print(self)
2008
2009 self._messages = []
2010 self._failures = []
2011
2012
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002013class MetaProject(Project):
2014 """A special project housed under .repo.
2015 """
2016 def __init__(self, manifest, name, gitdir, worktree):
2017 repodir = manifest.repodir
2018 Project.__init__(self,
2019 manifest = manifest,
2020 name = name,
2021 gitdir = gitdir,
2022 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002023 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002024 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002025 revisionExpr = 'refs/heads/master',
2026 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002027
2028 def PreSync(self):
2029 if self.Exists:
2030 cb = self.CurrentBranch
2031 if cb:
2032 base = self.GetBranch(cb).merge
2033 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002034 self.revisionExpr = base
2035 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036
2037 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002038 def LastFetch(self):
2039 try:
2040 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2041 return os.path.getmtime(fh)
2042 except OSError:
2043 return 0
2044
2045 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002046 def HasChanges(self):
2047 """Has the remote received new commits not yet checked out?
2048 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002049 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002050 return False
2051
2052 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002053 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002054 head = self.work_git.GetHead()
2055 if head.startswith(R_HEADS):
2056 try:
2057 head = all[head]
2058 except KeyError:
2059 head = None
2060
2061 if revid == head:
2062 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002063 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002064 return True
2065 return False