blob: 8b9a3fc44d10188f99f5b8fe8bfce41793f2e4c5 [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
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070032try:
33 from os import SEEK_END
34except ImportError:
35 SEEK_END = 2
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037from color import Coloring
38from git_command import GitCommand
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070039from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070040from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080041from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080042from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070043from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070047_urllib_lock = _threading.Lock()
48
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070049def _lwrite(path, content):
50 lock = '%s.lock' % path
51
52 fd = open(lock, 'wb')
53 try:
54 fd.write(content)
55 finally:
56 fd.close()
57
58 try:
59 os.rename(lock, path)
60 except OSError:
61 os.remove(lock)
62 raise
63
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
66 print >>sys.stderr, 'error: %s' % msg
67
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068def not_rev(r):
69 return '^' + r
70
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080071def sq(r):
72 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080073
Doug Anderson8ced8642011-01-10 14:16:30 -080074_project_hook_list = None
75def _ProjectHooks():
76 """List the hooks present in the 'hooks' directory.
77
78 These hooks are project hooks and are copied to the '.git/hooks' directory
79 of all subprojects.
80
81 This function caches the list of hooks (based on the contents of the
82 'repo/hooks' directory) on the first call.
83
84 Returns:
85 A list of absolute paths to all of the files in the hooks directory.
86 """
87 global _project_hook_list
88 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089 d = os.path.abspath(os.path.dirname(__file__))
90 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080091 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
92 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080093
94def relpath(dst, src):
95 src = os.path.dirname(src)
96 top = os.path.commonprefix([dst, src])
97 if top.endswith('/'):
98 top = top[:-1]
99 else:
100 top = os.path.dirname(top)
101
102 tmp = src
103 rel = ''
104 while top != tmp:
105 rel += '../'
106 tmp = os.path.dirname(tmp)
107 return rel + dst[len(top) + 1:]
108
109
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700110class DownloadedChange(object):
111 _commit_cache = None
112
113 def __init__(self, project, base, change_id, ps_id, commit):
114 self.project = project
115 self.base = base
116 self.change_id = change_id
117 self.ps_id = ps_id
118 self.commit = commit
119
120 @property
121 def commits(self):
122 if self._commit_cache is None:
123 self._commit_cache = self.project.bare_git.rev_list(
124 '--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
150 self._commit_cache = self.project.bare_git.rev_list(
151 '--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
164 for commit in self.project.bare_git.rev_list(
165 not_rev(self.base),
166 R_HEADS + self.name,
167 '--'):
168 r[commit[0:8]] = commit
169 return r
170
171 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 def date(self):
173 return self.project.bare_git.log(
174 '--pretty=format:%cd',
175 '-n', '1',
176 R_HEADS + self.name,
177 '--')
178
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700179 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
182 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700184 def GetPublishedRefs(self):
185 refs = {}
186 output = self.project.bare_git.ls_remote(
187 self.branch.remote.SshReviewUrl(self.project.UserEmail),
188 'refs/changes/*')
189 for line in output.split('\n'):
190 try:
191 (sha, ref) = line.split()
192 refs[sha] = ref
193 except ValueError:
194 pass
195
196 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
198class StatusColoring(Coloring):
199 def __init__(self, config):
200 Coloring.__init__(self, config, 'status')
201 self.project = self.printer('header', attr = 'bold')
202 self.branch = self.printer('header', attr = 'bold')
203 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700204 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206 self.added = self.printer('added', fg = 'green')
207 self.changed = self.printer('changed', fg = 'red')
208 self.untracked = self.printer('untracked', fg = 'red')
209
210
211class DiffColoring(Coloring):
212 def __init__(self, config):
213 Coloring.__init__(self, config, 'diff')
214 self.project = self.printer('header', attr = 'bold')
215
216
217class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800218 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219 self.src = src
220 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800221 self.abs_src = abssrc
222 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
224 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800225 src = self.abs_src
226 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 # copy file if it does not exist or is out of date
228 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
229 try:
230 # remove existing file first, since it might be read-only
231 if os.path.exists(dest):
232 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400233 else:
234 dir = os.path.dirname(dest)
235 if not os.path.isdir(dir):
236 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 shutil.copy(src, dest)
238 # make the file read-only
239 mode = os.stat(dest)[stat.ST_MODE]
240 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
241 os.chmod(dest, mode)
242 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700243 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700245class RemoteSpec(object):
246 def __init__(self,
247 name,
248 url = None,
249 review = None):
250 self.name = name
251 self.url = url
252 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Doug Anderson37282b42011-03-04 11:54:18 -0800254class RepoHook(object):
255 """A RepoHook contains information about a script to run as a hook.
256
257 Hooks are used to run a python script before running an upload (for instance,
258 to run presubmit checks). Eventually, we may have hooks for other actions.
259
260 This shouldn't be confused with files in the 'repo/hooks' directory. Those
261 files are copied into each '.git/hooks' folder for each project. Repo-level
262 hooks are associated instead with repo actions.
263
264 Hooks are always python. When a hook is run, we will load the hook into the
265 interpreter and execute its main() function.
266 """
267 def __init__(self,
268 hook_type,
269 hooks_project,
270 topdir,
271 abort_if_user_denies=False):
272 """RepoHook constructor.
273
274 Params:
275 hook_type: A string representing the type of hook. This is also used
276 to figure out the name of the file containing the hook. For
277 example: 'pre-upload'.
278 hooks_project: The project containing the repo hooks. If you have a
279 manifest, this is manifest.repo_hooks_project. OK if this is None,
280 which will make the hook a no-op.
281 topdir: Repo's top directory (the one containing the .repo directory).
282 Scripts will run with CWD as this directory. If you have a manifest,
283 this is manifest.topdir
284 abort_if_user_denies: If True, we'll throw a HookError() if the user
285 doesn't allow us to run the hook.
286 """
287 self._hook_type = hook_type
288 self._hooks_project = hooks_project
289 self._topdir = topdir
290 self._abort_if_user_denies = abort_if_user_denies
291
292 # Store the full path to the script for convenience.
293 if self._hooks_project:
294 self._script_fullpath = os.path.join(self._hooks_project.worktree,
295 self._hook_type + '.py')
296 else:
297 self._script_fullpath = None
298
299 def _GetHash(self):
300 """Return a hash of the contents of the hooks directory.
301
302 We'll just use git to do this. This hash has the property that if anything
303 changes in the directory we will return a different has.
304
305 SECURITY CONSIDERATION:
306 This hash only represents the contents of files in the hook directory, not
307 any other files imported or called by hooks. Changes to imported files
308 can change the script behavior without affecting the hash.
309
310 Returns:
311 A string representing the hash. This will always be ASCII so that it can
312 be printed to the user easily.
313 """
314 assert self._hooks_project, "Must have hooks to calculate their hash."
315
316 # We will use the work_git object rather than just calling GetRevisionId().
317 # That gives us a hash of the latest checked in version of the files that
318 # the user will actually be executing. Specifically, GetRevisionId()
319 # doesn't appear to change even if a user checks out a different version
320 # of the hooks repo (via git checkout) nor if a user commits their own revs.
321 #
322 # NOTE: Local (non-committed) changes will not be factored into this hash.
323 # I think this is OK, since we're really only worried about warning the user
324 # about upstream changes.
325 return self._hooks_project.work_git.rev_parse('HEAD')
326
327 def _GetMustVerb(self):
328 """Return 'must' if the hook is required; 'should' if not."""
329 if self._abort_if_user_denies:
330 return 'must'
331 else:
332 return 'should'
333
334 def _CheckForHookApproval(self):
335 """Check to see whether this hook has been approved.
336
337 We'll look at the hash of all of the hooks. If this matches the hash that
338 the user last approved, we're done. If it doesn't, we'll ask the user
339 about approval.
340
341 Note that we ask permission for each individual hook even though we use
342 the hash of all hooks when detecting changes. We'd like the user to be
343 able to approve / deny each hook individually. We only use the hash of all
344 hooks because there is no other easy way to detect changes to local imports.
345
346 Returns:
347 True if this hook is approved to run; False otherwise.
348
349 Raises:
350 HookError: Raised if the user doesn't approve and abort_if_user_denies
351 was passed to the consturctor.
352 """
353 hooks_dir = self._hooks_project.worktree
354 hooks_config = self._hooks_project.config
355 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
356
357 # Get the last hash that the user approved for this hook; may be None.
358 old_hash = hooks_config.GetString(git_approval_key)
359
360 # Get the current hash so we can tell if scripts changed since approval.
361 new_hash = self._GetHash()
362
363 if old_hash is not None:
364 # User previously approved hook and asked not to be prompted again.
365 if new_hash == old_hash:
366 # Approval matched. We're done.
367 return True
368 else:
369 # Give the user a reason why we're prompting, since they last told
370 # us to "never ask again".
371 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
372 self._hook_type)
373 else:
374 prompt = ''
375
376 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
377 if sys.stdout.isatty():
378 prompt += ('Repo %s run the script:\n'
379 ' %s\n'
380 '\n'
381 'Do you want to allow this script to run '
382 '(yes/yes-never-ask-again/NO)? ') % (
383 self._GetMustVerb(), self._script_fullpath)
384 response = raw_input(prompt).lower()
385 print
386
387 # User is doing a one-time approval.
388 if response in ('y', 'yes'):
389 return True
390 elif response == 'yes-never-ask-again':
391 hooks_config.SetString(git_approval_key, new_hash)
392 return True
393
394 # For anything else, we'll assume no approval.
395 if self._abort_if_user_denies:
396 raise HookError('You must allow the %s hook or use --no-verify.' %
397 self._hook_type)
398
399 return False
400
401 def _ExecuteHook(self, **kwargs):
402 """Actually execute the given hook.
403
404 This will run the hook's 'main' function in our python interpreter.
405
406 Args:
407 kwargs: Keyword arguments to pass to the hook. These are often specific
408 to the hook type. For instance, pre-upload hooks will contain
409 a project_list.
410 """
411 # Keep sys.path and CWD stashed away so that we can always restore them
412 # upon function exit.
413 orig_path = os.getcwd()
414 orig_syspath = sys.path
415
416 try:
417 # Always run hooks with CWD as topdir.
418 os.chdir(self._topdir)
419
420 # Put the hook dir as the first item of sys.path so hooks can do
421 # relative imports. We want to replace the repo dir as [0] so
422 # hooks can't import repo files.
423 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
424
425 # Exec, storing global context in the context dict. We catch exceptions
426 # and convert to a HookError w/ just the failing traceback.
427 context = {}
428 try:
429 execfile(self._script_fullpath, context)
430 except Exception:
431 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
432 traceback.format_exc(), self._hook_type))
433
434 # Running the script should have defined a main() function.
435 if 'main' not in context:
436 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
437
438
439 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
440 # We don't actually want hooks to define their main with this argument--
441 # it's there to remind them that their hook should always take **kwargs.
442 # For instance, a pre-upload hook should be defined like:
443 # def main(project_list, **kwargs):
444 #
445 # This allows us to later expand the API without breaking old hooks.
446 kwargs = kwargs.copy()
447 kwargs['hook_should_take_kwargs'] = True
448
449 # Call the main function in the hook. If the hook should cause the
450 # build to fail, it will raise an Exception. We'll catch that convert
451 # to a HookError w/ just the failing traceback.
452 try:
453 context['main'](**kwargs)
454 except Exception:
455 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
456 'above.' % (
457 traceback.format_exc(), self._hook_type))
458 finally:
459 # Restore sys.path and CWD.
460 sys.path = orig_syspath
461 os.chdir(orig_path)
462
463 def Run(self, user_allows_all_hooks, **kwargs):
464 """Run the hook.
465
466 If the hook doesn't exist (because there is no hooks project or because
467 this particular hook is not enabled), this is a no-op.
468
469 Args:
470 user_allows_all_hooks: If True, we will never prompt about running the
471 hook--we'll just assume it's OK to run it.
472 kwargs: Keyword arguments to pass to the hook. These are often specific
473 to the hook type. For instance, pre-upload hooks will contain
474 a project_list.
475
476 Raises:
477 HookError: If there was a problem finding the hook or the user declined
478 to run a required hook (from _CheckForHookApproval).
479 """
480 # No-op if there is no hooks project or if hook is disabled.
481 if ((not self._hooks_project) or
482 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
483 return
484
485 # Bail with a nice error if we can't find the hook.
486 if not os.path.isfile(self._script_fullpath):
487 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
488
489 # Make sure the user is OK with running the hook.
490 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
491 return
492
493 # Run the hook with the same version of python we're using.
494 self._ExecuteHook(**kwargs)
495
496
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700497class Project(object):
498 def __init__(self,
499 manifest,
500 name,
501 remote,
502 gitdir,
503 worktree,
504 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700505 revisionExpr,
506 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700507 self.manifest = manifest
508 self.name = name
509 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800510 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800511 if worktree:
512 self.worktree = worktree.replace('\\', '/')
513 else:
514 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700516 self.revisionExpr = revisionExpr
517
518 if revisionId is None \
519 and revisionExpr \
520 and IsId(revisionExpr):
521 self.revisionId = revisionExpr
522 else:
523 self.revisionId = revisionId
524
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 self.copyfiles = []
527 self.config = GitConfig.ForRepository(
528 gitdir = self.gitdir,
529 defaults = self.manifest.globalConfig)
530
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800531 if self.worktree:
532 self.work_git = self._GitGetByExec(self, bare=False)
533 else:
534 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700535 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700536 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537
Doug Anderson37282b42011-03-04 11:54:18 -0800538 # This will be filled in if a project is later identified to be the
539 # project containing repo hooks.
540 self.enabled_repo_hooks = []
541
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 @property
543 def Exists(self):
544 return os.path.isdir(self.gitdir)
545
546 @property
547 def CurrentBranch(self):
548 """Obtain the name of the currently checked out branch.
549 The branch name omits the 'refs/heads/' prefix.
550 None is returned if the project is on a detached HEAD.
551 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700552 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 if b.startswith(R_HEADS):
554 return b[len(R_HEADS):]
555 return None
556
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700557 def IsRebaseInProgress(self):
558 w = self.worktree
559 g = os.path.join(w, '.git')
560 return os.path.exists(os.path.join(g, 'rebase-apply')) \
561 or os.path.exists(os.path.join(g, 'rebase-merge')) \
562 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def IsDirty(self, consider_untracked=True):
565 """Is the working directory modified in some way?
566 """
567 self.work_git.update_index('-q',
568 '--unmerged',
569 '--ignore-missing',
570 '--refresh')
571 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
572 return True
573 if self.work_git.DiffZ('diff-files'):
574 return True
575 if consider_untracked and self.work_git.LsOthers():
576 return True
577 return False
578
579 _userident_name = None
580 _userident_email = None
581
582 @property
583 def UserName(self):
584 """Obtain the user's personal name.
585 """
586 if self._userident_name is None:
587 self._LoadUserIdentity()
588 return self._userident_name
589
590 @property
591 def UserEmail(self):
592 """Obtain the user's email address. This is very likely
593 to be their Gerrit login.
594 """
595 if self._userident_email is None:
596 self._LoadUserIdentity()
597 return self._userident_email
598
599 def _LoadUserIdentity(self):
600 u = self.bare_git.var('GIT_COMMITTER_IDENT')
601 m = re.compile("^(.*) <([^>]*)> ").match(u)
602 if m:
603 self._userident_name = m.group(1)
604 self._userident_email = m.group(2)
605 else:
606 self._userident_name = ''
607 self._userident_email = ''
608
609 def GetRemote(self, name):
610 """Get the configuration for a single remote.
611 """
612 return self.config.GetRemote(name)
613
614 def GetBranch(self, name):
615 """Get the configuration for a single branch.
616 """
617 return self.config.GetBranch(name)
618
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700619 def GetBranches(self):
620 """Get all existing local branches.
621 """
622 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700623 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700624 heads = {}
625 pubd = {}
626
627 for name, id in all.iteritems():
628 if name.startswith(R_HEADS):
629 name = name[len(R_HEADS):]
630 b = self.GetBranch(name)
631 b.current = name == current
632 b.published = None
633 b.revision = id
634 heads[name] = b
635
636 for name, id in all.iteritems():
637 if name.startswith(R_PUB):
638 name = name[len(R_PUB):]
639 b = heads.get(name)
640 if b:
641 b.published = id
642
643 return heads
644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646## Status Display ##
647
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500648 def HasChanges(self):
649 """Returns true if there are uncommitted changes.
650 """
651 self.work_git.update_index('-q',
652 '--unmerged',
653 '--ignore-missing',
654 '--refresh')
655 if self.IsRebaseInProgress():
656 return True
657
658 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
659 return True
660
661 if self.work_git.DiffZ('diff-files'):
662 return True
663
664 if self.work_git.LsOthers():
665 return True
666
667 return False
668
Terence Haddock4655e812011-03-31 12:33:34 +0200669 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700670 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200671
672 Args:
673 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 """
675 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200676 if output_redir == None:
677 output_redir = sys.stdout
678 print >>output_redir, ''
679 print >>output_redir, 'project %s/' % self.relpath
680 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681 return
682
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700687 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
689 df = self.work_git.DiffZ('diff-files')
690 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700691 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700692 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693
694 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200695 if not output_redir == None:
696 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 out.project('project %-40s', self.relpath + '/')
698
699 branch = self.CurrentBranch
700 if branch is None:
701 out.nobranch('(*** NO BRANCH ***)')
702 else:
703 out.branch('branch %s', branch)
704 out.nl()
705
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700706 if rb:
707 out.important('prior sync failed; rebase still in progress')
708 out.nl()
709
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710 paths = list()
711 paths.extend(di.keys())
712 paths.extend(df.keys())
713 paths.extend(do)
714
715 paths = list(set(paths))
716 paths.sort()
717
718 for p in paths:
719 try: i = di[p]
720 except KeyError: i = None
721
722 try: f = df[p]
723 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 if i: i_status = i.status.upper()
726 else: i_status = '-'
727
728 if f: f_status = f.status.lower()
729 else: f_status = '-'
730
731 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800732 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 i.src_path, p, i.level)
734 else:
735 line = ' %s%s\t%s' % (i_status, f_status, p)
736
737 if i and not f:
738 out.added('%s', line)
739 elif (i and f) or (not i and f):
740 out.changed('%s', line)
741 elif not i and not f:
742 out.untracked('%s', line)
743 else:
744 out.write('%s', line)
745 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200746
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700747 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749 def PrintWorkTreeDiff(self):
750 """Prints the status of the repository to stdout.
751 """
752 out = DiffColoring(self.config)
753 cmd = ['diff']
754 if out.is_on:
755 cmd.append('--color')
756 cmd.append(HEAD)
757 cmd.append('--')
758 p = GitCommand(self,
759 cmd,
760 capture_stdout = True,
761 capture_stderr = True)
762 has_diff = False
763 for line in p.process.stdout:
764 if not has_diff:
765 out.nl()
766 out.project('project %s/' % self.relpath)
767 out.nl()
768 has_diff = True
769 print line[:-1]
770 p.Wait()
771
772
773## Publish / Upload ##
774
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700775 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 """Was the branch published (uploaded) for code review?
777 If so, returns the SHA-1 hash of the last published
778 state for the branch.
779 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700780 key = R_PUB + branch
781 if all is None:
782 try:
783 return self.bare_git.rev_parse(key)
784 except GitError:
785 return None
786 else:
787 try:
788 return all[key]
789 except KeyError:
790 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700792 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793 """Prunes any stale published refs.
794 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700795 if all is None:
796 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 heads = set()
798 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700799 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 if name.startswith(R_HEADS):
801 heads.add(name)
802 elif name.startswith(R_PUB):
803 canrm[name] = id
804
805 for name, id in canrm.iteritems():
806 n = name[len(R_PUB):]
807 if R_HEADS + n not in heads:
808 self.bare_git.DeleteRef(name, id)
809
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700810 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 """List any branches which can be uploaded for review.
812 """
813 heads = {}
814 pubed = {}
815
816 for name, id in self._allrefs.iteritems():
817 if name.startswith(R_HEADS):
818 heads[name[len(R_HEADS):]] = id
819 elif name.startswith(R_PUB):
820 pubed[name[len(R_PUB):]] = id
821
822 ready = []
823 for branch, id in heads.iteritems():
824 if branch in pubed and pubed[branch] == id:
825 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700826 if selected_branch and branch != selected_branch:
827 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800829 rb = self.GetUploadableBranch(branch)
830 if rb:
831 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 return ready
833
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800834 def GetUploadableBranch(self, branch_name):
835 """Get a single uploadable branch, or None.
836 """
837 branch = self.GetBranch(branch_name)
838 base = branch.LocalMerge
839 if branch.LocalMerge:
840 rb = ReviewableBranch(self, branch, base)
841 if rb.commits:
842 return rb
843 return None
844
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700845 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700846 people=([],[]),
847 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 """Uploads the named branch for code review.
849 """
850 if branch is None:
851 branch = self.CurrentBranch
852 if branch is None:
853 raise GitError('not currently on a branch')
854
855 branch = self.GetBranch(branch)
856 if not branch.LocalMerge:
857 raise GitError('branch %s does not track a remote' % branch.name)
858 if not branch.remote.review:
859 raise GitError('remote %s has no review url' % branch.remote.name)
860
861 dest_branch = branch.merge
862 if not dest_branch.startswith(R_HEADS):
863 dest_branch = R_HEADS + dest_branch
864
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800865 if not branch.remote.projectname:
866 branch.remote.projectname = self.name
867 branch.remote.Save()
868
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800869 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800870 if dest_branch.startswith(R_HEADS):
871 dest_branch = dest_branch[len(R_HEADS):]
872
873 rp = ['gerrit receive-pack']
874 for e in people[0]:
875 rp.append('--reviewer=%s' % sq(e))
876 for e in people[1]:
877 rp.append('--cc=%s' % sq(e))
878
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700879 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
880 if auto_topic:
881 ref_spec = ref_spec + '/' + branch.name
882
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800883 cmd = ['push']
884 cmd.append('--receive-pack=%s' % " ".join(rp))
885 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700886 cmd.append(ref_spec)
887
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800888 if GitCommand(self, cmd, bare = True).Wait() != 0:
889 raise UploadError('Upload failed')
890
891 else:
892 raise UploadError('Unsupported protocol %s' \
893 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894
895 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
896 self.bare_git.UpdateRef(R_PUB + branch.name,
897 R_HEADS + branch.name,
898 message = msg)
899
900
901## Sync ##
902
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700903 def Sync_NetworkHalf(self, quiet=False, is_new=None, current_branch_only=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 """Perform only the network IO portion of the sync process.
905 Local working directory/branch state is not affected.
906 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700907 if is_new is None:
908 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200909 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 self._InitGitDir()
911 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700912
913 if is_new:
914 alt = os.path.join(self.gitdir, 'objects/info/alternates')
915 try:
916 fd = open(alt, 'rb')
917 try:
918 alt_dir = fd.readline().rstrip()
919 finally:
920 fd.close()
921 except IOError:
922 alt_dir = None
923 else:
924 alt_dir = None
925
926 if alt_dir is None and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
927 is_new = False
928
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700929 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
930 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800932
933 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800934 self._InitMRef()
935 else:
936 self._InitMirrorHead()
937 try:
938 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
939 except OSError:
940 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800942
943 def PostRepoUpgrade(self):
944 self._InitHooks()
945
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 def _CopyFiles(self):
947 for file in self.copyfiles:
948 file._Copy()
949
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700950 def GetRevisionId(self, all=None):
951 if self.revisionId:
952 return self.revisionId
953
954 rem = self.GetRemote(self.remote.name)
955 rev = rem.ToLocal(self.revisionExpr)
956
957 if all is not None and rev in all:
958 return all[rev]
959
960 try:
961 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
962 except GitError:
963 raise ManifestInvalidRevisionError(
964 'revision %s in %s not found' % (self.revisionExpr,
965 self.name))
966
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700967 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 """Perform only the local IO portion of the sync process.
969 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700971 all = self.bare_ref.all
972 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700973 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800974
975 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700976 head = self.work_git.GetHead()
977 if head.startswith(R_HEADS):
978 branch = head[len(R_HEADS):]
979 try:
980 head = all[head]
981 except KeyError:
982 head = None
983 else:
984 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700986 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 # Currently on a detached HEAD. The user is assumed to
988 # not have any local modifications worth worrying about.
989 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700990 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700991 syncbuf.fail(self, _PriorSyncFailedError())
992 return
993
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700994 if head == revid:
995 # No changes; don't do anything further.
996 #
997 return
998
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700999 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001001 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001003 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001004 except GitError, e:
1005 syncbuf.fail(self, e)
1006 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001008 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001010 if head == revid:
1011 # No changes; don't do anything further.
1012 #
1013 return
1014
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001017 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001019 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001021 syncbuf.info(self,
1022 "leaving %s; does not track upstream",
1023 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001025 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001026 except GitError, e:
1027 syncbuf.fail(self, e)
1028 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001030 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001032 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001033 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001035 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 if not_merged:
1037 if upstream_gain:
1038 # The user has published this branch and some of those
1039 # commits are not yet merged upstream. We do not want
1040 # to rewrite the published commits so we punt.
1041 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001042 syncbuf.fail(self,
1043 "branch %s is published (but not merged) and is now %d commits behind"
1044 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001045 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001046 elif pub == head:
1047 # All published commits are merged, and thus we are a
1048 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001049 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001050 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001051 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001052 self._CopyFiles()
1053 syncbuf.later1(self, _doff)
1054 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001056 # Examine the local commits not in the remote. Find the
1057 # last one attributed to this user, if any.
1058 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001059 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001060 last_mine = None
1061 cnt_mine = 0
1062 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001063 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001064 if committer_email == self.UserEmail:
1065 last_mine = commit_id
1066 cnt_mine += 1
1067
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001068 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001069 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
1071 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001072 syncbuf.fail(self, _DirtyError())
1073 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001075 # If the upstream switched on us, warn the user.
1076 #
1077 if branch.merge != self.revisionExpr:
1078 if branch.merge and self.revisionExpr:
1079 syncbuf.info(self,
1080 'manifest switched %s...%s',
1081 branch.merge,
1082 self.revisionExpr)
1083 elif branch.merge:
1084 syncbuf.info(self,
1085 'manifest no longer tracks %s',
1086 branch.merge)
1087
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001088 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001090 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001092 syncbuf.info(self,
1093 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001094 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001096 branch.remote = self.GetRemote(self.remote.name)
1097 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098 branch.Save()
1099
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001100 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001101 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001102 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 self._CopyFiles()
1104 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001107 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001108 self._CopyFiles()
1109 except GitError, e:
1110 syncbuf.fail(self, e)
1111 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001114 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001115 self._CopyFiles()
1116 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001118 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 # dest should already be an absolute path, but src is project relative
1120 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001121 abssrc = os.path.join(self.worktree, src)
1122 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001124 def DownloadPatchSet(self, change_id, patch_id):
1125 """Download a single patch set of a single change to FETCH_HEAD.
1126 """
1127 remote = self.GetRemote(self.remote.name)
1128
1129 cmd = ['fetch', remote.name]
1130 cmd.append('refs/changes/%2.2d/%d/%d' \
1131 % (change_id % 100, change_id, patch_id))
1132 cmd.extend(map(lambda x: str(x), remote.fetch))
1133 if GitCommand(self, cmd, bare=True).Wait() != 0:
1134 return None
1135 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001136 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001137 change_id,
1138 patch_id,
1139 self.bare_git.rev_parse('FETCH_HEAD'))
1140
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141
1142## Branch Management ##
1143
1144 def StartBranch(self, name):
1145 """Create a new branch off the manifest's revision.
1146 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001147 head = self.work_git.GetHead()
1148 if head == (R_HEADS + name):
1149 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001151 all = self.bare_ref.all
1152 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001153 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001154 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001155 capture_stdout = True,
1156 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001157
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001158 branch = self.GetBranch(name)
1159 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001160 branch.merge = self.revisionExpr
1161 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001162
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001163 if head.startswith(R_HEADS):
1164 try:
1165 head = all[head]
1166 except KeyError:
1167 head = None
1168
1169 if revid and head and revid == head:
1170 ref = os.path.join(self.gitdir, R_HEADS + name)
1171 try:
1172 os.makedirs(os.path.dirname(ref))
1173 except OSError:
1174 pass
1175 _lwrite(ref, '%s\n' % revid)
1176 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1177 'ref: %s%s\n' % (R_HEADS, name))
1178 branch.Save()
1179 return True
1180
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001181 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001183 capture_stdout = True,
1184 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001185 branch.Save()
1186 return True
1187 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188
Wink Saville02d79452009-04-10 13:01:24 -07001189 def CheckoutBranch(self, name):
1190 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001191
1192 Args:
1193 name: The name of the branch to checkout.
1194
1195 Returns:
1196 True if the checkout succeeded; False if it didn't; None if the branch
1197 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001198 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001199 rev = R_HEADS + name
1200 head = self.work_git.GetHead()
1201 if head == rev:
1202 # Already on the branch
1203 #
1204 return True
Wink Saville02d79452009-04-10 13:01:24 -07001205
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001206 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001207 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001208 revid = all[rev]
1209 except KeyError:
1210 # Branch does not exist in this project
1211 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001212 return None
Wink Saville02d79452009-04-10 13:01:24 -07001213
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001214 if head.startswith(R_HEADS):
1215 try:
1216 head = all[head]
1217 except KeyError:
1218 head = None
1219
1220 if head == revid:
1221 # Same revision; just update HEAD to point to the new
1222 # target branch, but otherwise take no other action.
1223 #
1224 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1225 'ref: %s%s\n' % (R_HEADS, name))
1226 return True
1227
1228 return GitCommand(self,
1229 ['checkout', name, '--'],
1230 capture_stdout = True,
1231 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001232
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001233 def AbandonBranch(self, name):
1234 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001235
1236 Args:
1237 name: The name of the branch to abandon.
1238
1239 Returns:
1240 True if the abandon succeeded; False if it didn't; None if the branch
1241 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001242 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001243 rev = R_HEADS + name
1244 all = self.bare_ref.all
1245 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001246 # Doesn't exist
1247 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001248
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001249 head = self.work_git.GetHead()
1250 if head == rev:
1251 # We can't destroy the branch while we are sitting
1252 # on it. Switch to a detached HEAD.
1253 #
1254 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001255
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001256 revid = self.GetRevisionId(all)
1257 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001258 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1259 '%s\n' % revid)
1260 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001261 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001262
1263 return GitCommand(self,
1264 ['branch', '-D', name],
1265 capture_stdout = True,
1266 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001267
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268 def PruneHeads(self):
1269 """Prune any topic branches already merged into upstream.
1270 """
1271 cb = self.CurrentBranch
1272 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001273 left = self._allrefs
1274 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275 if name.startswith(R_HEADS):
1276 name = name[len(R_HEADS):]
1277 if cb is None or name != cb:
1278 kill.append(name)
1279
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 if cb is not None \
1282 and not self._revlist(HEAD + '...' + rev) \
1283 and not self.IsDirty(consider_untracked = False):
1284 self.work_git.DetachHead(HEAD)
1285 kill.append(cb)
1286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001288 old = self.bare_git.GetHead()
1289 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 try:
1293 self.bare_git.DetachHead(rev)
1294
1295 b = ['branch', '-d']
1296 b.extend(kill)
1297 b = GitCommand(self, b, bare=True,
1298 capture_stdout=True,
1299 capture_stderr=True)
1300 b.Wait()
1301 finally:
1302 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001303 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001305 for branch in kill:
1306 if (R_HEADS + branch) not in left:
1307 self.CleanPublishedCache()
1308 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309
1310 if cb and cb not in kill:
1311 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001312 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313
1314 kept = []
1315 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001316 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 branch = self.GetBranch(branch)
1318 base = branch.LocalMerge
1319 if not base:
1320 base = rev
1321 kept.append(ReviewableBranch(self, branch, base))
1322 return kept
1323
1324
1325## Direct Git Commands ##
1326
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001327 def _RemoteFetch(self, name=None,
1328 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001329 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001330 quiet=False,
1331 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001332
1333 is_sha1 = False
1334 tag_name = None
1335
1336 if current_branch_only:
1337 if ID_RE.match(self.revisionExpr) is not None:
1338 is_sha1 = True
1339 elif self.revisionExpr.startswith(R_TAGS):
1340 # this is a tag and its sha1 value should never change
1341 tag_name = self.revisionExpr[len(R_TAGS):]
1342
1343 if is_sha1 or tag_name is not None:
1344 try:
1345 self.GetRevisionId()
1346 return True
1347 except ManifestInvalidRevisionError:
1348 # There is no such persistent revision. We have to fetch it.
1349 pass
1350
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351 if not name:
1352 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001353
1354 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001355 remote = self.GetRemote(name)
1356 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001357 ssh_proxy = True
1358
Shawn O. Pearce88443382010-10-08 10:02:09 +02001359 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001360 if alt_dir and 'objects' == os.path.basename(alt_dir):
1361 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001362 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1363 remote = self.GetRemote(name)
1364
1365 all = self.bare_ref.all
1366 ids = set(all.values())
1367 tmp = set()
1368
1369 for r, id in GitRefs(ref_dir).all.iteritems():
1370 if r not in all:
1371 if r.startswith(R_TAGS) or remote.WritesTo(r):
1372 all[r] = id
1373 ids.add(id)
1374 continue
1375
1376 if id in ids:
1377 continue
1378
1379 r = 'refs/_alt/%s' % id
1380 all[r] = id
1381 ids.add(id)
1382 tmp.add(r)
1383
1384 ref_names = list(all.keys())
1385 ref_names.sort()
1386
1387 tmp_packed = ''
1388 old_packed = ''
1389
1390 for r in ref_names:
1391 line = '%s %s\n' % (all[r], r)
1392 tmp_packed += line
1393 if r not in tmp:
1394 old_packed += line
1395
1396 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001397 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001398 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001399
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001400 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001401
1402 # The --depth option only affects the initial fetch; after that we'll do
1403 # full fetches of changes.
1404 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1405 if depth and initial:
1406 cmd.append('--depth=%s' % depth)
1407
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001408 if quiet:
1409 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001410 if not self.worktree:
1411 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001412 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001413
1414 if not current_branch_only or is_sha1:
1415 # Fetch whole repo
1416 cmd.append('--tags')
1417 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1418 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001419 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001420 cmd.append(tag_name)
1421 else:
1422 branch = self.revisionExpr
1423 if branch.startswith(R_HEADS):
1424 branch = branch[len(R_HEADS):]
1425 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001426
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001427 ok = False
1428 for i in range(2):
1429 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1430 ok = True
1431 break
1432 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001433
1434 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001435 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001436 if old_packed != '':
1437 _lwrite(packed_refs, old_packed)
1438 else:
1439 os.remove(packed_refs)
1440 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001441 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001442
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001443 def _ApplyCloneBundle(self, initial=False, quiet=False):
1444 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1445 return False
1446
1447 remote = self.GetRemote(self.remote.name)
1448 bundle_url = remote.url + '/clone.bundle'
1449 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1450 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1451 return False
1452
1453 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1454 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1455
1456 exist_dst = os.path.exists(bundle_dst)
1457 exist_tmp = os.path.exists(bundle_tmp)
1458
1459 if not initial and not exist_dst and not exist_tmp:
1460 return False
1461
1462 if not exist_dst:
1463 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1464 if not exist_dst:
1465 return False
1466
1467 cmd = ['fetch']
1468 if quiet:
1469 cmd.append('--quiet')
1470 if not self.worktree:
1471 cmd.append('--update-head-ok')
1472 cmd.append(bundle_dst)
1473 for f in remote.fetch:
1474 cmd.append(str(f))
1475 cmd.append('refs/tags/*:refs/tags/*')
1476
1477 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001478 if os.path.exists(bundle_dst):
1479 os.remove(bundle_dst)
1480 if os.path.exists(bundle_tmp):
1481 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001482 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001484 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001485 keep = True
1486 done = False
1487 dest = open(tmpPath, 'a+b')
1488 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001489 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001490 pos = dest.tell()
1491
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001492 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001493 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001494 req = urllib2.Request(srcUrl)
1495 if pos > 0:
1496 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001497
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001498 try:
1499 r = urllib2.urlopen(req)
1500 except urllib2.HTTPError, e:
1501 def _content_type():
1502 try:
1503 return e.info()['content-type']
1504 except:
1505 return None
1506
1507 if e.code == 404:
1508 keep = False
1509 return False
1510 elif _content_type() == 'text/plain':
1511 try:
1512 msg = e.read()
1513 if len(msg) > 0 and msg[-1] == '\n':
1514 msg = msg[0:-1]
1515 msg = ' (%s)' % msg
1516 except:
1517 msg = ''
1518 else:
1519 try:
1520 from BaseHTTPServer import BaseHTTPRequestHandler
1521 res = BaseHTTPRequestHandler.responses[e.code]
1522 msg = ' (%s: %s)' % (res[0], res[1])
1523 except:
1524 msg = ''
1525 raise DownloadError('HTTP %s%s' % (e.code, msg))
1526 except urllib2.URLError, e:
1527 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1528 finally:
1529 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001530
1531 p = None
1532 try:
1533 size = r.headers['content-length']
1534 unit = 1 << 10
1535
1536 if size and not quiet:
1537 if size > 1024 * 1.3:
1538 unit = 1 << 20
1539 desc = 'MB'
1540 else:
1541 desc = 'KB'
1542 p = Progress(
1543 'Downloading %s' % self.relpath,
1544 int(size) / unit,
1545 units=desc)
1546 if pos > 0:
1547 p.update(pos / unit)
1548
1549 s = 0
1550 while True:
1551 d = r.read(8192)
1552 if d == '':
1553 done = True
1554 return True
1555 dest.write(d)
1556 if p:
1557 s += len(d)
1558 if s >= unit:
1559 p.update(s / unit)
1560 s = s % unit
1561 if p:
1562 if s >= unit:
1563 p.update(s / unit)
1564 else:
1565 p.update(1)
1566 finally:
1567 r.close()
1568 if p:
1569 p.end()
1570 finally:
1571 dest.close()
1572
1573 if os.path.exists(dstPath):
1574 os.remove(dstPath)
1575 if done:
1576 os.rename(tmpPath, dstPath)
1577 elif not keep:
1578 os.remove(tmpPath)
1579
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001580 def _Checkout(self, rev, quiet=False):
1581 cmd = ['checkout']
1582 if quiet:
1583 cmd.append('-q')
1584 cmd.append(rev)
1585 cmd.append('--')
1586 if GitCommand(self, cmd).Wait() != 0:
1587 if self._allrefs:
1588 raise GitError('%s checkout %s ' % (self.name, rev))
1589
1590 def _ResetHard(self, rev, quiet=True):
1591 cmd = ['reset', '--hard']
1592 if quiet:
1593 cmd.append('-q')
1594 cmd.append(rev)
1595 if GitCommand(self, cmd).Wait() != 0:
1596 raise GitError('%s reset --hard %s ' % (self.name, rev))
1597
1598 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001599 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 if onto is not None:
1601 cmd.extend(['--onto', onto])
1602 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001603 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 raise GitError('%s rebase %s ' % (self.name, upstream))
1605
1606 def _FastForward(self, head):
1607 cmd = ['merge', head]
1608 if GitCommand(self, cmd).Wait() != 0:
1609 raise GitError('%s merge %s ' % (self.name, head))
1610
1611 def _InitGitDir(self):
1612 if not os.path.exists(self.gitdir):
1613 os.makedirs(self.gitdir)
1614 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001615
Shawn O. Pearce88443382010-10-08 10:02:09 +02001616 mp = self.manifest.manifestProject
1617 ref_dir = mp.config.GetString('repo.reference')
1618
1619 if ref_dir:
1620 mirror_git = os.path.join(ref_dir, self.name + '.git')
1621 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1622 self.relpath + '.git')
1623
1624 if os.path.exists(mirror_git):
1625 ref_dir = mirror_git
1626
1627 elif os.path.exists(repo_git):
1628 ref_dir = repo_git
1629
1630 else:
1631 ref_dir = None
1632
1633 if ref_dir:
1634 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1635 os.path.join(ref_dir, 'objects') + '\n')
1636
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001637 if self.manifest.IsMirror:
1638 self.config.SetString('core.bare', 'true')
1639 else:
1640 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641
1642 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001643 try:
1644 to_rm = os.listdir(hooks)
1645 except OSError:
1646 to_rm = []
1647 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001649 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650
1651 m = self.manifest.manifestProject.config
1652 for key in ['user.name', 'user.email']:
1653 if m.Has(key, include_defaults = False):
1654 self.config.SetString(key, m.GetString(key))
1655
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001656 def _InitHooks(self):
1657 hooks = self._gitdir_path('hooks')
1658 if not os.path.exists(hooks):
1659 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001660 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001661 name = os.path.basename(stock_hook)
1662
Victor Boivie65e0f352011-04-18 11:23:29 +02001663 if name in ('commit-msg',) and not self.remote.review \
1664 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001665 # Don't install a Gerrit Code Review hook if this
1666 # project does not appear to use it for reviews.
1667 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001668 # Since the manifest project is one of those, but also
1669 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001670 continue
1671
1672 dst = os.path.join(hooks, name)
1673 if os.path.islink(dst):
1674 continue
1675 if os.path.exists(dst):
1676 if filecmp.cmp(stock_hook, dst, shallow=False):
1677 os.remove(dst)
1678 else:
1679 _error("%s: Not replacing %s hook", self.relpath, name)
1680 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001681 try:
1682 os.symlink(relpath(stock_hook, dst), dst)
1683 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001684 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001685 raise GitError('filesystem must support symlinks')
1686 else:
1687 raise
1688
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001689 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001690 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001692 remote.url = self.remote.url
1693 remote.review = self.remote.review
1694 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001696 if self.worktree:
1697 remote.ResetFetch(mirror=False)
1698 else:
1699 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700 remote.Save()
1701
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001702 def _InitMRef(self):
1703 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001704 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001706 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001707 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001708
1709 def _InitAnyMRef(self, ref):
1710 cur = self.bare_ref.symref(ref)
1711
1712 if self.revisionId:
1713 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1714 msg = 'manifest set to %s' % self.revisionId
1715 dst = self.revisionId + '^0'
1716 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1717 else:
1718 remote = self.GetRemote(self.remote.name)
1719 dst = remote.ToLocal(self.revisionExpr)
1720 if cur != dst:
1721 msg = 'manifest set to %s' % self.revisionExpr
1722 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001723
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724 def _InitWorkTree(self):
1725 dotgit = os.path.join(self.worktree, '.git')
1726 if not os.path.exists(dotgit):
1727 os.makedirs(dotgit)
1728
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729 for name in ['config',
1730 'description',
1731 'hooks',
1732 'info',
1733 'logs',
1734 'objects',
1735 'packed-refs',
1736 'refs',
1737 'rr-cache',
1738 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001739 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001740 src = os.path.join(self.gitdir, name)
1741 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001742 if os.path.islink(dst) or not os.path.exists(dst):
1743 os.symlink(relpath(src, dst), dst)
1744 else:
1745 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001746 except OSError, e:
1747 if e.errno == errno.EPERM:
1748 raise GitError('filesystem must support symlinks')
1749 else:
1750 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001752 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
1754 cmd = ['read-tree', '--reset', '-u']
1755 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001756 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757 if GitCommand(self, cmd).Wait() != 0:
1758 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001759
1760 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1761 if not os.path.exists(rr_cache):
1762 os.makedirs(rr_cache)
1763
Shawn O. Pearce93609662009-04-21 10:50:33 -07001764 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765
1766 def _gitdir_path(self, path):
1767 return os.path.join(self.gitdir, path)
1768
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001769 def _revlist(self, *args, **kw):
1770 a = []
1771 a.extend(args)
1772 a.append('--')
1773 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774
1775 @property
1776 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001777 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778
1779 class _GitGetByExec(object):
1780 def __init__(self, project, bare):
1781 self._project = project
1782 self._bare = bare
1783
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 def LsOthers(self):
1785 p = GitCommand(self._project,
1786 ['ls-files',
1787 '-z',
1788 '--others',
1789 '--exclude-standard'],
1790 bare = False,
1791 capture_stdout = True,
1792 capture_stderr = True)
1793 if p.Wait() == 0:
1794 out = p.stdout
1795 if out:
1796 return out[:-1].split("\0")
1797 return []
1798
1799 def DiffZ(self, name, *args):
1800 cmd = [name]
1801 cmd.append('-z')
1802 cmd.extend(args)
1803 p = GitCommand(self._project,
1804 cmd,
1805 bare = False,
1806 capture_stdout = True,
1807 capture_stderr = True)
1808 try:
1809 out = p.process.stdout.read()
1810 r = {}
1811 if out:
1812 out = iter(out[:-1].split('\0'))
1813 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001814 try:
1815 info = out.next()
1816 path = out.next()
1817 except StopIteration:
1818 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001819
1820 class _Info(object):
1821 def __init__(self, path, omode, nmode, oid, nid, state):
1822 self.path = path
1823 self.src_path = None
1824 self.old_mode = omode
1825 self.new_mode = nmode
1826 self.old_id = oid
1827 self.new_id = nid
1828
1829 if len(state) == 1:
1830 self.status = state
1831 self.level = None
1832 else:
1833 self.status = state[:1]
1834 self.level = state[1:]
1835 while self.level.startswith('0'):
1836 self.level = self.level[1:]
1837
1838 info = info[1:].split(' ')
1839 info =_Info(path, *info)
1840 if info.status in ('R', 'C'):
1841 info.src_path = info.path
1842 info.path = out.next()
1843 r[info.path] = info
1844 return r
1845 finally:
1846 p.Wait()
1847
1848 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001849 if self._bare:
1850 path = os.path.join(self._project.gitdir, HEAD)
1851 else:
1852 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001853 fd = open(path, 'rb')
1854 try:
1855 line = fd.read()
1856 finally:
1857 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001858 if line.startswith('ref: '):
1859 return line[5:-1]
1860 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861
1862 def SetHead(self, ref, message=None):
1863 cmdv = []
1864 if message is not None:
1865 cmdv.extend(['-m', message])
1866 cmdv.append(HEAD)
1867 cmdv.append(ref)
1868 self.symbolic_ref(*cmdv)
1869
1870 def DetachHead(self, new, message=None):
1871 cmdv = ['--no-deref']
1872 if message is not None:
1873 cmdv.extend(['-m', message])
1874 cmdv.append(HEAD)
1875 cmdv.append(new)
1876 self.update_ref(*cmdv)
1877
1878 def UpdateRef(self, name, new, old=None,
1879 message=None,
1880 detach=False):
1881 cmdv = []
1882 if message is not None:
1883 cmdv.extend(['-m', message])
1884 if detach:
1885 cmdv.append('--no-deref')
1886 cmdv.append(name)
1887 cmdv.append(new)
1888 if old is not None:
1889 cmdv.append(old)
1890 self.update_ref(*cmdv)
1891
1892 def DeleteRef(self, name, old=None):
1893 if not old:
1894 old = self.rev_parse(name)
1895 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001896 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001897
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001898 def rev_list(self, *args, **kw):
1899 if 'format' in kw:
1900 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1901 else:
1902 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001903 cmdv.extend(args)
1904 p = GitCommand(self._project,
1905 cmdv,
1906 bare = self._bare,
1907 capture_stdout = True,
1908 capture_stderr = True)
1909 r = []
1910 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001911 if line[-1] == '\n':
1912 line = line[:-1]
1913 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001914 if p.Wait() != 0:
1915 raise GitError('%s rev-list %s: %s' % (
1916 self._project.name,
1917 str(args),
1918 p.stderr))
1919 return r
1920
1921 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001922 """Allow arbitrary git commands using pythonic syntax.
1923
1924 This allows you to do things like:
1925 git_obj.rev_parse('HEAD')
1926
1927 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1928 run. We'll replace the '_' with a '-' and try to run a git command.
1929 Any other arguments will be passed to the git command.
1930
1931 Args:
1932 name: The name of the git command to call. Any '_' characters will
1933 be replaced with '-'.
1934
1935 Returns:
1936 A callable object that will try to call git with the named command.
1937 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938 name = name.replace('_', '-')
1939 def runner(*args):
1940 cmdv = [name]
1941 cmdv.extend(args)
1942 p = GitCommand(self._project,
1943 cmdv,
1944 bare = self._bare,
1945 capture_stdout = True,
1946 capture_stderr = True)
1947 if p.Wait() != 0:
1948 raise GitError('%s %s: %s' % (
1949 self._project.name,
1950 name,
1951 p.stderr))
1952 r = p.stdout
1953 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1954 return r[:-1]
1955 return r
1956 return runner
1957
1958
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001959class _PriorSyncFailedError(Exception):
1960 def __str__(self):
1961 return 'prior sync failed; rebase still in progress'
1962
1963class _DirtyError(Exception):
1964 def __str__(self):
1965 return 'contains uncommitted changes'
1966
1967class _InfoMessage(object):
1968 def __init__(self, project, text):
1969 self.project = project
1970 self.text = text
1971
1972 def Print(self, syncbuf):
1973 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1974 syncbuf.out.nl()
1975
1976class _Failure(object):
1977 def __init__(self, project, why):
1978 self.project = project
1979 self.why = why
1980
1981 def Print(self, syncbuf):
1982 syncbuf.out.fail('error: %s/: %s',
1983 self.project.relpath,
1984 str(self.why))
1985 syncbuf.out.nl()
1986
1987class _Later(object):
1988 def __init__(self, project, action):
1989 self.project = project
1990 self.action = action
1991
1992 def Run(self, syncbuf):
1993 out = syncbuf.out
1994 out.project('project %s/', self.project.relpath)
1995 out.nl()
1996 try:
1997 self.action()
1998 out.nl()
1999 return True
2000 except GitError, e:
2001 out.nl()
2002 return False
2003
2004class _SyncColoring(Coloring):
2005 def __init__(self, config):
2006 Coloring.__init__(self, config, 'reposync')
2007 self.project = self.printer('header', attr = 'bold')
2008 self.info = self.printer('info')
2009 self.fail = self.printer('fail', fg='red')
2010
2011class SyncBuffer(object):
2012 def __init__(self, config, detach_head=False):
2013 self._messages = []
2014 self._failures = []
2015 self._later_queue1 = []
2016 self._later_queue2 = []
2017
2018 self.out = _SyncColoring(config)
2019 self.out.redirect(sys.stderr)
2020
2021 self.detach_head = detach_head
2022 self.clean = True
2023
2024 def info(self, project, fmt, *args):
2025 self._messages.append(_InfoMessage(project, fmt % args))
2026
2027 def fail(self, project, err=None):
2028 self._failures.append(_Failure(project, err))
2029 self.clean = False
2030
2031 def later1(self, project, what):
2032 self._later_queue1.append(_Later(project, what))
2033
2034 def later2(self, project, what):
2035 self._later_queue2.append(_Later(project, what))
2036
2037 def Finish(self):
2038 self._PrintMessages()
2039 self._RunLater()
2040 self._PrintMessages()
2041 return self.clean
2042
2043 def _RunLater(self):
2044 for q in ['_later_queue1', '_later_queue2']:
2045 if not self._RunQueue(q):
2046 return
2047
2048 def _RunQueue(self, queue):
2049 for m in getattr(self, queue):
2050 if not m.Run(self):
2051 self.clean = False
2052 return False
2053 setattr(self, queue, [])
2054 return True
2055
2056 def _PrintMessages(self):
2057 for m in self._messages:
2058 m.Print(self)
2059 for m in self._failures:
2060 m.Print(self)
2061
2062 self._messages = []
2063 self._failures = []
2064
2065
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002066class MetaProject(Project):
2067 """A special project housed under .repo.
2068 """
2069 def __init__(self, manifest, name, gitdir, worktree):
2070 repodir = manifest.repodir
2071 Project.__init__(self,
2072 manifest = manifest,
2073 name = name,
2074 gitdir = gitdir,
2075 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002076 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002077 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002078 revisionExpr = 'refs/heads/master',
2079 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002080
2081 def PreSync(self):
2082 if self.Exists:
2083 cb = self.CurrentBranch
2084 if cb:
2085 base = self.GetBranch(cb).merge
2086 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002087 self.revisionExpr = base
2088 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089
2090 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002091 def LastFetch(self):
2092 try:
2093 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2094 return os.path.getmtime(fh)
2095 except OSError:
2096 return 0
2097
2098 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002099 def HasChanges(self):
2100 """Has the remote received new commits not yet checked out?
2101 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002102 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002103 return False
2104
2105 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002106 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002107 head = self.work_git.GetHead()
2108 if head.startswith(R_HEADS):
2109 try:
2110 head = all[head]
2111 except KeyError:
2112 head = None
2113
2114 if revid == head:
2115 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002116 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117 return True
2118 return False