blob: b80ad3199fe71f0f5984446e76e5bffff2370bd3 [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,
Mike Pontillod3153822012-02-28 11:53:24 -0800506 revisionId,
507 rebase = True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700508 self.manifest = manifest
509 self.name = name
510 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800511 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800512 if worktree:
513 self.worktree = worktree.replace('\\', '/')
514 else:
515 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700517 self.revisionExpr = revisionExpr
518
519 if revisionId is None \
520 and revisionExpr \
521 and IsId(revisionExpr):
522 self.revisionId = revisionExpr
523 else:
524 self.revisionId = revisionId
525
Mike Pontillod3153822012-02-28 11:53:24 -0800526 self.rebase = rebase
527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 self.copyfiles = []
530 self.config = GitConfig.ForRepository(
531 gitdir = self.gitdir,
532 defaults = self.manifest.globalConfig)
533
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800534 if self.worktree:
535 self.work_git = self._GitGetByExec(self, bare=False)
536 else:
537 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700539 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540
Doug Anderson37282b42011-03-04 11:54:18 -0800541 # This will be filled in if a project is later identified to be the
542 # project containing repo hooks.
543 self.enabled_repo_hooks = []
544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545 @property
546 def Exists(self):
547 return os.path.isdir(self.gitdir)
548
549 @property
550 def CurrentBranch(self):
551 """Obtain the name of the currently checked out branch.
552 The branch name omits the 'refs/heads/' prefix.
553 None is returned if the project is on a detached HEAD.
554 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700555 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 if b.startswith(R_HEADS):
557 return b[len(R_HEADS):]
558 return None
559
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700560 def IsRebaseInProgress(self):
561 w = self.worktree
562 g = os.path.join(w, '.git')
563 return os.path.exists(os.path.join(g, 'rebase-apply')) \
564 or os.path.exists(os.path.join(g, 'rebase-merge')) \
565 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200566
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 def IsDirty(self, consider_untracked=True):
568 """Is the working directory modified in some way?
569 """
570 self.work_git.update_index('-q',
571 '--unmerged',
572 '--ignore-missing',
573 '--refresh')
574 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
575 return True
576 if self.work_git.DiffZ('diff-files'):
577 return True
578 if consider_untracked and self.work_git.LsOthers():
579 return True
580 return False
581
582 _userident_name = None
583 _userident_email = None
584
585 @property
586 def UserName(self):
587 """Obtain the user's personal name.
588 """
589 if self._userident_name is None:
590 self._LoadUserIdentity()
591 return self._userident_name
592
593 @property
594 def UserEmail(self):
595 """Obtain the user's email address. This is very likely
596 to be their Gerrit login.
597 """
598 if self._userident_email is None:
599 self._LoadUserIdentity()
600 return self._userident_email
601
602 def _LoadUserIdentity(self):
603 u = self.bare_git.var('GIT_COMMITTER_IDENT')
604 m = re.compile("^(.*) <([^>]*)> ").match(u)
605 if m:
606 self._userident_name = m.group(1)
607 self._userident_email = m.group(2)
608 else:
609 self._userident_name = ''
610 self._userident_email = ''
611
612 def GetRemote(self, name):
613 """Get the configuration for a single remote.
614 """
615 return self.config.GetRemote(name)
616
617 def GetBranch(self, name):
618 """Get the configuration for a single branch.
619 """
620 return self.config.GetBranch(name)
621
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700622 def GetBranches(self):
623 """Get all existing local branches.
624 """
625 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700626 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700627 heads = {}
628 pubd = {}
629
630 for name, id in all.iteritems():
631 if name.startswith(R_HEADS):
632 name = name[len(R_HEADS):]
633 b = self.GetBranch(name)
634 b.current = name == current
635 b.published = None
636 b.revision = id
637 heads[name] = b
638
639 for name, id in all.iteritems():
640 if name.startswith(R_PUB):
641 name = name[len(R_PUB):]
642 b = heads.get(name)
643 if b:
644 b.published = id
645
646 return heads
647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700648
649## Status Display ##
650
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500651 def HasChanges(self):
652 """Returns true if there are uncommitted changes.
653 """
654 self.work_git.update_index('-q',
655 '--unmerged',
656 '--ignore-missing',
657 '--refresh')
658 if self.IsRebaseInProgress():
659 return True
660
661 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
662 return True
663
664 if self.work_git.DiffZ('diff-files'):
665 return True
666
667 if self.work_git.LsOthers():
668 return True
669
670 return False
671
Terence Haddock4655e812011-03-31 12:33:34 +0200672 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700673 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200674
675 Args:
676 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677 """
678 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200679 if output_redir == None:
680 output_redir = sys.stdout
681 print >>output_redir, ''
682 print >>output_redir, 'project %s/' % self.relpath
683 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 return
685
686 self.work_git.update_index('-q',
687 '--unmerged',
688 '--ignore-missing',
689 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700690 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
692 df = self.work_git.DiffZ('diff-files')
693 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100694 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700695 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696
697 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200698 if not output_redir == None:
699 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 out.project('project %-40s', self.relpath + '/')
701
702 branch = self.CurrentBranch
703 if branch is None:
704 out.nobranch('(*** NO BRANCH ***)')
705 else:
706 out.branch('branch %s', branch)
707 out.nl()
708
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700709 if rb:
710 out.important('prior sync failed; rebase still in progress')
711 out.nl()
712
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713 paths = list()
714 paths.extend(di.keys())
715 paths.extend(df.keys())
716 paths.extend(do)
717
718 paths = list(set(paths))
719 paths.sort()
720
721 for p in paths:
722 try: i = di[p]
723 except KeyError: i = None
724
725 try: f = df[p]
726 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728 if i: i_status = i.status.upper()
729 else: i_status = '-'
730
731 if f: f_status = f.status.lower()
732 else: f_status = '-'
733
734 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800735 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736 i.src_path, p, i.level)
737 else:
738 line = ' %s%s\t%s' % (i_status, f_status, p)
739
740 if i and not f:
741 out.added('%s', line)
742 elif (i and f) or (not i and f):
743 out.changed('%s', line)
744 elif not i and not f:
745 out.untracked('%s', line)
746 else:
747 out.write('%s', line)
748 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200749
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700750 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751
752 def PrintWorkTreeDiff(self):
753 """Prints the status of the repository to stdout.
754 """
755 out = DiffColoring(self.config)
756 cmd = ['diff']
757 if out.is_on:
758 cmd.append('--color')
759 cmd.append(HEAD)
760 cmd.append('--')
761 p = GitCommand(self,
762 cmd,
763 capture_stdout = True,
764 capture_stderr = True)
765 has_diff = False
766 for line in p.process.stdout:
767 if not has_diff:
768 out.nl()
769 out.project('project %s/' % self.relpath)
770 out.nl()
771 has_diff = True
772 print line[:-1]
773 p.Wait()
774
775
776## Publish / Upload ##
777
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700778 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779 """Was the branch published (uploaded) for code review?
780 If so, returns the SHA-1 hash of the last published
781 state for the branch.
782 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700783 key = R_PUB + branch
784 if all is None:
785 try:
786 return self.bare_git.rev_parse(key)
787 except GitError:
788 return None
789 else:
790 try:
791 return all[key]
792 except KeyError:
793 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700795 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700796 """Prunes any stale published refs.
797 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700798 if all is None:
799 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 heads = set()
801 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700802 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 if name.startswith(R_HEADS):
804 heads.add(name)
805 elif name.startswith(R_PUB):
806 canrm[name] = id
807
808 for name, id in canrm.iteritems():
809 n = name[len(R_PUB):]
810 if R_HEADS + n not in heads:
811 self.bare_git.DeleteRef(name, id)
812
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700813 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 """List any branches which can be uploaded for review.
815 """
816 heads = {}
817 pubed = {}
818
819 for name, id in self._allrefs.iteritems():
820 if name.startswith(R_HEADS):
821 heads[name[len(R_HEADS):]] = id
822 elif name.startswith(R_PUB):
823 pubed[name[len(R_PUB):]] = id
824
825 ready = []
826 for branch, id in heads.iteritems():
827 if branch in pubed and pubed[branch] == id:
828 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700829 if selected_branch and branch != selected_branch:
830 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800832 rb = self.GetUploadableBranch(branch)
833 if rb:
834 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 return ready
836
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800837 def GetUploadableBranch(self, branch_name):
838 """Get a single uploadable branch, or None.
839 """
840 branch = self.GetBranch(branch_name)
841 base = branch.LocalMerge
842 if branch.LocalMerge:
843 rb = ReviewableBranch(self, branch, base)
844 if rb.commits:
845 return rb
846 return None
847
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700848 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700849 people=([],[]),
850 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 """Uploads the named branch for code review.
852 """
853 if branch is None:
854 branch = self.CurrentBranch
855 if branch is None:
856 raise GitError('not currently on a branch')
857
858 branch = self.GetBranch(branch)
859 if not branch.LocalMerge:
860 raise GitError('branch %s does not track a remote' % branch.name)
861 if not branch.remote.review:
862 raise GitError('remote %s has no review url' % branch.remote.name)
863
864 dest_branch = branch.merge
865 if not dest_branch.startswith(R_HEADS):
866 dest_branch = R_HEADS + dest_branch
867
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800868 if not branch.remote.projectname:
869 branch.remote.projectname = self.name
870 branch.remote.Save()
871
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800872 url = branch.remote.ReviewUrl(self.UserEmail)
873 if url is None:
874 raise UploadError('review not configured')
875 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800876
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800877 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800878 rp = ['gerrit receive-pack']
879 for e in people[0]:
880 rp.append('--reviewer=%s' % sq(e))
881 for e in people[1]:
882 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800883 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700884
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800885 cmd.append(url)
886
887 if dest_branch.startswith(R_HEADS):
888 dest_branch = dest_branch[len(R_HEADS):]
889 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
890 if auto_topic:
891 ref_spec = ref_spec + '/' + branch.name
892 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800893
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800894 if GitCommand(self, cmd, bare = True).Wait() != 0:
895 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896
897 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
898 self.bare_git.UpdateRef(R_PUB + branch.name,
899 R_HEADS + branch.name,
900 message = msg)
901
902
903## Sync ##
904
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700905 def Sync_NetworkHalf(self,
906 quiet=False,
907 is_new=None,
908 current_branch_only=False,
909 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 """Perform only the network IO portion of the sync process.
911 Local working directory/branch state is not affected.
912 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700913 if is_new is None:
914 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200915 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 self._InitGitDir()
917 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700918
919 if is_new:
920 alt = os.path.join(self.gitdir, 'objects/info/alternates')
921 try:
922 fd = open(alt, 'rb')
923 try:
924 alt_dir = fd.readline().rstrip()
925 finally:
926 fd.close()
927 except IOError:
928 alt_dir = None
929 else:
930 alt_dir = None
931
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700932 if clone_bundle \
933 and alt_dir is None \
934 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700935 is_new = False
936
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700937 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
938 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800940
941 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800942 self._InitMRef()
943 else:
944 self._InitMirrorHead()
945 try:
946 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
947 except OSError:
948 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800950
951 def PostRepoUpgrade(self):
952 self._InitHooks()
953
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 def _CopyFiles(self):
955 for file in self.copyfiles:
956 file._Copy()
957
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700958 def GetRevisionId(self, all=None):
959 if self.revisionId:
960 return self.revisionId
961
962 rem = self.GetRemote(self.remote.name)
963 rev = rem.ToLocal(self.revisionExpr)
964
965 if all is not None and rev in all:
966 return all[rev]
967
968 try:
969 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
970 except GitError:
971 raise ManifestInvalidRevisionError(
972 'revision %s in %s not found' % (self.revisionExpr,
973 self.name))
974
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700975 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976 """Perform only the local IO portion of the sync process.
977 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700979 all = self.bare_ref.all
980 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700981 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800982
983 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700984 head = self.work_git.GetHead()
985 if head.startswith(R_HEADS):
986 branch = head[len(R_HEADS):]
987 try:
988 head = all[head]
989 except KeyError:
990 head = None
991 else:
992 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700994 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 # Currently on a detached HEAD. The user is assumed to
996 # not have any local modifications worth worrying about.
997 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700998 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700999 syncbuf.fail(self, _PriorSyncFailedError())
1000 return
1001
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001002 if head == revid:
1003 # No changes; don't do anything further.
1004 #
1005 return
1006
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001007 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001009 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001011 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001012 except GitError, e:
1013 syncbuf.fail(self, e)
1014 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001016 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001018 if head == revid:
1019 # No changes; don't do anything further.
1020 #
1021 return
1022
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001025 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001027 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001029 syncbuf.info(self,
1030 "leaving %s; does not track upstream",
1031 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001033 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001034 except GitError, e:
1035 syncbuf.fail(self, e)
1036 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001038 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001041 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001043 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 if not_merged:
1045 if upstream_gain:
1046 # The user has published this branch and some of those
1047 # commits are not yet merged upstream. We do not want
1048 # to rewrite the published commits so we punt.
1049 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001050 syncbuf.fail(self,
1051 "branch %s is published (but not merged) and is now %d commits behind"
1052 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001054 elif pub == head:
1055 # All published commits are merged, and thus we are a
1056 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001057 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001058 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001059 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001060 self._CopyFiles()
1061 syncbuf.later1(self, _doff)
1062 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001064 # Examine the local commits not in the remote. Find the
1065 # last one attributed to this user, if any.
1066 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001067 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001068 last_mine = None
1069 cnt_mine = 0
1070 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001071 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001072 if committer_email == self.UserEmail:
1073 last_mine = commit_id
1074 cnt_mine += 1
1075
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001076 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001077 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078
1079 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 syncbuf.fail(self, _DirtyError())
1081 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 # If the upstream switched on us, warn the user.
1084 #
1085 if branch.merge != self.revisionExpr:
1086 if branch.merge and self.revisionExpr:
1087 syncbuf.info(self,
1088 'manifest switched %s...%s',
1089 branch.merge,
1090 self.revisionExpr)
1091 elif branch.merge:
1092 syncbuf.info(self,
1093 'manifest no longer tracks %s',
1094 branch.merge)
1095
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001096 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001098 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001100 syncbuf.info(self,
1101 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001102 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001104 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001105 if not ID_RE.match(self.revisionExpr):
1106 # in case of manifest sync the revisionExpr might be a SHA1
1107 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 branch.Save()
1109
Mike Pontillod3153822012-02-28 11:53:24 -08001110 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001111 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001112 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 self._CopyFiles()
1114 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001115 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001117 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001118 self._CopyFiles()
1119 except GitError, e:
1120 syncbuf.fail(self, e)
1121 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001123 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001124 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001125 self._CopyFiles()
1126 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001128 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 # dest should already be an absolute path, but src is project relative
1130 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001131 abssrc = os.path.join(self.worktree, src)
1132 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001134 def DownloadPatchSet(self, change_id, patch_id):
1135 """Download a single patch set of a single change to FETCH_HEAD.
1136 """
1137 remote = self.GetRemote(self.remote.name)
1138
1139 cmd = ['fetch', remote.name]
1140 cmd.append('refs/changes/%2.2d/%d/%d' \
1141 % (change_id % 100, change_id, patch_id))
1142 cmd.extend(map(lambda x: str(x), remote.fetch))
1143 if GitCommand(self, cmd, bare=True).Wait() != 0:
1144 return None
1145 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001146 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001147 change_id,
1148 patch_id,
1149 self.bare_git.rev_parse('FETCH_HEAD'))
1150
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151
1152## Branch Management ##
1153
1154 def StartBranch(self, name):
1155 """Create a new branch off the manifest's revision.
1156 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001157 head = self.work_git.GetHead()
1158 if head == (R_HEADS + name):
1159 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001161 all = self.bare_ref.all
1162 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001163 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001164 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001165 capture_stdout = True,
1166 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001167
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001168 branch = self.GetBranch(name)
1169 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001170 branch.merge = self.revisionExpr
1171 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001172
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001173 if head.startswith(R_HEADS):
1174 try:
1175 head = all[head]
1176 except KeyError:
1177 head = None
1178
1179 if revid and head and revid == head:
1180 ref = os.path.join(self.gitdir, R_HEADS + name)
1181 try:
1182 os.makedirs(os.path.dirname(ref))
1183 except OSError:
1184 pass
1185 _lwrite(ref, '%s\n' % revid)
1186 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1187 'ref: %s%s\n' % (R_HEADS, name))
1188 branch.Save()
1189 return True
1190
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001191 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001192 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001193 capture_stdout = True,
1194 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001195 branch.Save()
1196 return True
1197 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001198
Wink Saville02d79452009-04-10 13:01:24 -07001199 def CheckoutBranch(self, name):
1200 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001201
1202 Args:
1203 name: The name of the branch to checkout.
1204
1205 Returns:
1206 True if the checkout succeeded; False if it didn't; None if the branch
1207 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001208 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001209 rev = R_HEADS + name
1210 head = self.work_git.GetHead()
1211 if head == rev:
1212 # Already on the branch
1213 #
1214 return True
Wink Saville02d79452009-04-10 13:01:24 -07001215
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001216 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001217 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001218 revid = all[rev]
1219 except KeyError:
1220 # Branch does not exist in this project
1221 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001222 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001223
1224 if head.startswith(R_HEADS):
1225 try:
1226 head = all[head]
1227 except KeyError:
1228 head = None
1229
1230 if head == revid:
1231 # Same revision; just update HEAD to point to the new
1232 # target branch, but otherwise take no other action.
1233 #
1234 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1235 'ref: %s%s\n' % (R_HEADS, name))
1236 return True
Wink Saville02d79452009-04-10 13:01:24 -07001237
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001238 return GitCommand(self,
1239 ['checkout', name, '--'],
1240 capture_stdout = True,
1241 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001242
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001243 def AbandonBranch(self, name):
1244 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001245
1246 Args:
1247 name: The name of the branch to abandon.
1248
1249 Returns:
1250 True if the abandon succeeded; False if it didn't; None if the branch
1251 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001252 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001253 rev = R_HEADS + name
1254 all = self.bare_ref.all
1255 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001256 # Doesn't exist
1257 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001258
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001259 head = self.work_git.GetHead()
1260 if head == rev:
1261 # We can't destroy the branch while we are sitting
1262 # on it. Switch to a detached HEAD.
1263 #
1264 head = all[head]
1265
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001266 revid = self.GetRevisionId(all)
1267 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001268 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1269 '%s\n' % revid)
1270 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001271 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001272
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001273 return GitCommand(self,
1274 ['branch', '-D', name],
1275 capture_stdout = True,
1276 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278 def PruneHeads(self):
1279 """Prune any topic branches already merged into upstream.
1280 """
1281 cb = self.CurrentBranch
1282 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001283 left = self._allrefs
1284 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285 if name.startswith(R_HEADS):
1286 name = name[len(R_HEADS):]
1287 if cb is None or name != cb:
1288 kill.append(name)
1289
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 if cb is not None \
1292 and not self._revlist(HEAD + '...' + rev) \
1293 and not self.IsDirty(consider_untracked = False):
1294 self.work_git.DetachHead(HEAD)
1295 kill.append(cb)
1296
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001298 old = self.bare_git.GetHead()
1299 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1301
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 try:
1303 self.bare_git.DetachHead(rev)
1304
1305 b = ['branch', '-d']
1306 b.extend(kill)
1307 b = GitCommand(self, b, bare=True,
1308 capture_stdout=True,
1309 capture_stderr=True)
1310 b.Wait()
1311 finally:
1312 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001313 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001315 for branch in kill:
1316 if (R_HEADS + branch) not in left:
1317 self.CleanPublishedCache()
1318 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319
1320 if cb and cb not in kill:
1321 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001322 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323
1324 kept = []
1325 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001326 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 branch = self.GetBranch(branch)
1328 base = branch.LocalMerge
1329 if not base:
1330 base = rev
1331 kept.append(ReviewableBranch(self, branch, base))
1332 return kept
1333
1334
1335## Direct Git Commands ##
1336
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001337 def _RemoteFetch(self, name=None,
1338 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001339 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001340 quiet=False,
1341 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001342
1343 is_sha1 = False
1344 tag_name = None
1345
1346 if current_branch_only:
1347 if ID_RE.match(self.revisionExpr) is not None:
1348 is_sha1 = True
1349 elif self.revisionExpr.startswith(R_TAGS):
1350 # this is a tag and its sha1 value should never change
1351 tag_name = self.revisionExpr[len(R_TAGS):]
1352
1353 if is_sha1 or tag_name is not None:
1354 try:
1355 self.GetRevisionId()
1356 return True
1357 except ManifestInvalidRevisionError:
1358 # There is no such persistent revision. We have to fetch it.
1359 pass
1360
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361 if not name:
1362 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001363
1364 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001365 remote = self.GetRemote(name)
1366 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001367 ssh_proxy = True
1368
Shawn O. Pearce88443382010-10-08 10:02:09 +02001369 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001370 if alt_dir and 'objects' == os.path.basename(alt_dir):
1371 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001372 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1373 remote = self.GetRemote(name)
1374
1375 all = self.bare_ref.all
1376 ids = set(all.values())
1377 tmp = set()
1378
1379 for r, id in GitRefs(ref_dir).all.iteritems():
1380 if r not in all:
1381 if r.startswith(R_TAGS) or remote.WritesTo(r):
1382 all[r] = id
1383 ids.add(id)
1384 continue
1385
1386 if id in ids:
1387 continue
1388
1389 r = 'refs/_alt/%s' % id
1390 all[r] = id
1391 ids.add(id)
1392 tmp.add(r)
1393
1394 ref_names = list(all.keys())
1395 ref_names.sort()
1396
1397 tmp_packed = ''
1398 old_packed = ''
1399
1400 for r in ref_names:
1401 line = '%s %s\n' % (all[r], r)
1402 tmp_packed += line
1403 if r not in tmp:
1404 old_packed += line
1405
1406 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001407 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001408 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001409
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001410 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001411
1412 # The --depth option only affects the initial fetch; after that we'll do
1413 # full fetches of changes.
1414 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1415 if depth and initial:
1416 cmd.append('--depth=%s' % depth)
1417
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001418 if quiet:
1419 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001420 if not self.worktree:
1421 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001422 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001423
1424 if not current_branch_only or is_sha1:
1425 # Fetch whole repo
1426 cmd.append('--tags')
1427 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1428 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001429 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001430 cmd.append(tag_name)
1431 else:
1432 branch = self.revisionExpr
1433 if branch.startswith(R_HEADS):
1434 branch = branch[len(R_HEADS):]
1435 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001436
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001437 ok = False
1438 for i in range(2):
1439 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1440 ok = True
1441 break
1442 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001443
1444 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001445 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001446 if old_packed != '':
1447 _lwrite(packed_refs, old_packed)
1448 else:
1449 os.remove(packed_refs)
1450 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001451 return ok
1452
1453 def _ApplyCloneBundle(self, initial=False, quiet=False):
1454 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1455 return False
1456
1457 remote = self.GetRemote(self.remote.name)
1458 bundle_url = remote.url + '/clone.bundle'
1459 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001460 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1461 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001462 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1463 return False
1464
1465 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1466 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001467
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001468 exist_dst = os.path.exists(bundle_dst)
1469 exist_tmp = os.path.exists(bundle_tmp)
1470
1471 if not initial and not exist_dst and not exist_tmp:
1472 return False
1473
1474 if not exist_dst:
1475 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1476 if not exist_dst:
1477 return False
1478
1479 cmd = ['fetch']
1480 if quiet:
1481 cmd.append('--quiet')
1482 if not self.worktree:
1483 cmd.append('--update-head-ok')
1484 cmd.append(bundle_dst)
1485 for f in remote.fetch:
1486 cmd.append(str(f))
1487 cmd.append('refs/tags/*:refs/tags/*')
1488
1489 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001490 if os.path.exists(bundle_dst):
1491 os.remove(bundle_dst)
1492 if os.path.exists(bundle_tmp):
1493 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001494 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001496 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001497 keep = True
1498 done = False
1499 dest = open(tmpPath, 'a+b')
1500 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001501 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001502 pos = dest.tell()
1503
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001504 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001505 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001506 req = urllib2.Request(srcUrl)
1507 if pos > 0:
1508 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001509
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001510 try:
1511 r = urllib2.urlopen(req)
1512 except urllib2.HTTPError, e:
1513 def _content_type():
1514 try:
1515 return e.info()['content-type']
1516 except:
1517 return None
1518
1519 if e.code == 404:
1520 keep = False
1521 return False
1522 elif _content_type() == 'text/plain':
1523 try:
1524 msg = e.read()
1525 if len(msg) > 0 and msg[-1] == '\n':
1526 msg = msg[0:-1]
1527 msg = ' (%s)' % msg
1528 except:
1529 msg = ''
1530 else:
1531 try:
1532 from BaseHTTPServer import BaseHTTPRequestHandler
1533 res = BaseHTTPRequestHandler.responses[e.code]
1534 msg = ' (%s: %s)' % (res[0], res[1])
1535 except:
1536 msg = ''
1537 raise DownloadError('HTTP %s%s' % (e.code, msg))
1538 except urllib2.URLError, e:
1539 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1540 finally:
1541 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001542
1543 p = None
1544 try:
Conley Owens43bda842012-03-12 11:25:04 -07001545 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001546 unit = 1 << 10
1547
1548 if size and not quiet:
1549 if size > 1024 * 1.3:
1550 unit = 1 << 20
1551 desc = 'MB'
1552 else:
1553 desc = 'KB'
1554 p = Progress(
1555 'Downloading %s' % self.relpath,
1556 int(size) / unit,
1557 units=desc)
1558 if pos > 0:
1559 p.update(pos / unit)
1560
1561 s = 0
1562 while True:
1563 d = r.read(8192)
1564 if d == '':
1565 done = True
1566 return True
1567 dest.write(d)
1568 if p:
1569 s += len(d)
1570 if s >= unit:
1571 p.update(s / unit)
1572 s = s % unit
1573 if p:
1574 if s >= unit:
1575 p.update(s / unit)
1576 else:
1577 p.update(1)
1578 finally:
1579 r.close()
1580 if p:
1581 p.end()
1582 finally:
1583 dest.close()
1584
1585 if os.path.exists(dstPath):
1586 os.remove(dstPath)
1587 if done:
1588 os.rename(tmpPath, dstPath)
1589 elif not keep:
1590 os.remove(tmpPath)
1591
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592 def _Checkout(self, rev, quiet=False):
1593 cmd = ['checkout']
1594 if quiet:
1595 cmd.append('-q')
1596 cmd.append(rev)
1597 cmd.append('--')
1598 if GitCommand(self, cmd).Wait() != 0:
1599 if self._allrefs:
1600 raise GitError('%s checkout %s ' % (self.name, rev))
1601
1602 def _ResetHard(self, rev, quiet=True):
1603 cmd = ['reset', '--hard']
1604 if quiet:
1605 cmd.append('-q')
1606 cmd.append(rev)
1607 if GitCommand(self, cmd).Wait() != 0:
1608 raise GitError('%s reset --hard %s ' % (self.name, rev))
1609
1610 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001611 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001612 if onto is not None:
1613 cmd.extend(['--onto', onto])
1614 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001615 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001616 raise GitError('%s rebase %s ' % (self.name, upstream))
1617
1618 def _FastForward(self, head):
1619 cmd = ['merge', head]
1620 if GitCommand(self, cmd).Wait() != 0:
1621 raise GitError('%s merge %s ' % (self.name, head))
1622
1623 def _InitGitDir(self):
1624 if not os.path.exists(self.gitdir):
1625 os.makedirs(self.gitdir)
1626 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001627
Shawn O. Pearce88443382010-10-08 10:02:09 +02001628 mp = self.manifest.manifestProject
1629 ref_dir = mp.config.GetString('repo.reference')
1630
1631 if ref_dir:
1632 mirror_git = os.path.join(ref_dir, self.name + '.git')
1633 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1634 self.relpath + '.git')
1635
1636 if os.path.exists(mirror_git):
1637 ref_dir = mirror_git
1638
1639 elif os.path.exists(repo_git):
1640 ref_dir = repo_git
1641
1642 else:
1643 ref_dir = None
1644
1645 if ref_dir:
1646 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1647 os.path.join(ref_dir, 'objects') + '\n')
1648
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001649 if self.manifest.IsMirror:
1650 self.config.SetString('core.bare', 'true')
1651 else:
1652 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001653
1654 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001655 try:
1656 to_rm = os.listdir(hooks)
1657 except OSError:
1658 to_rm = []
1659 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001661 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662
1663 m = self.manifest.manifestProject.config
1664 for key in ['user.name', 'user.email']:
1665 if m.Has(key, include_defaults = False):
1666 self.config.SetString(key, m.GetString(key))
1667
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001668 def _InitHooks(self):
1669 hooks = self._gitdir_path('hooks')
1670 if not os.path.exists(hooks):
1671 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001672 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001673 name = os.path.basename(stock_hook)
1674
Victor Boivie65e0f352011-04-18 11:23:29 +02001675 if name in ('commit-msg',) and not self.remote.review \
1676 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001677 # Don't install a Gerrit Code Review hook if this
1678 # project does not appear to use it for reviews.
1679 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001680 # Since the manifest project is one of those, but also
1681 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001682 continue
1683
1684 dst = os.path.join(hooks, name)
1685 if os.path.islink(dst):
1686 continue
1687 if os.path.exists(dst):
1688 if filecmp.cmp(stock_hook, dst, shallow=False):
1689 os.remove(dst)
1690 else:
1691 _error("%s: Not replacing %s hook", self.relpath, name)
1692 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001693 try:
1694 os.symlink(relpath(stock_hook, dst), dst)
1695 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001696 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001697 raise GitError('filesystem must support symlinks')
1698 else:
1699 raise
1700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001702 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001704 remote.url = self.remote.url
1705 remote.review = self.remote.review
1706 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001708 if self.worktree:
1709 remote.ResetFetch(mirror=False)
1710 else:
1711 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712 remote.Save()
1713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001714 def _InitMRef(self):
1715 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001716 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001718 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001719 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001720
1721 def _InitAnyMRef(self, ref):
1722 cur = self.bare_ref.symref(ref)
1723
1724 if self.revisionId:
1725 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1726 msg = 'manifest set to %s' % self.revisionId
1727 dst = self.revisionId + '^0'
1728 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1729 else:
1730 remote = self.GetRemote(self.remote.name)
1731 dst = remote.ToLocal(self.revisionExpr)
1732 if cur != dst:
1733 msg = 'manifest set to %s' % self.revisionExpr
1734 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736 def _InitWorkTree(self):
1737 dotgit = os.path.join(self.worktree, '.git')
1738 if not os.path.exists(dotgit):
1739 os.makedirs(dotgit)
1740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 for name in ['config',
1742 'description',
1743 'hooks',
1744 'info',
1745 'logs',
1746 'objects',
1747 'packed-refs',
1748 'refs',
1749 'rr-cache',
1750 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001751 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001752 src = os.path.join(self.gitdir, name)
1753 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001754 if os.path.islink(dst) or not os.path.exists(dst):
1755 os.symlink(relpath(src, dst), dst)
1756 else:
1757 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001758 except OSError, e:
1759 if e.errno == errno.EPERM:
1760 raise GitError('filesystem must support symlinks')
1761 else:
1762 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001764 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765
1766 cmd = ['read-tree', '--reset', '-u']
1767 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001768 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 if GitCommand(self, cmd).Wait() != 0:
1770 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001771
1772 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1773 if not os.path.exists(rr_cache):
1774 os.makedirs(rr_cache)
1775
Shawn O. Pearce93609662009-04-21 10:50:33 -07001776 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
1778 def _gitdir_path(self, path):
1779 return os.path.join(self.gitdir, path)
1780
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001781 def _revlist(self, *args, **kw):
1782 a = []
1783 a.extend(args)
1784 a.append('--')
1785 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786
1787 @property
1788 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001789 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790
1791 class _GitGetByExec(object):
1792 def __init__(self, project, bare):
1793 self._project = project
1794 self._bare = bare
1795
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796 def LsOthers(self):
1797 p = GitCommand(self._project,
1798 ['ls-files',
1799 '-z',
1800 '--others',
1801 '--exclude-standard'],
1802 bare = False,
1803 capture_stdout = True,
1804 capture_stderr = True)
1805 if p.Wait() == 0:
1806 out = p.stdout
1807 if out:
1808 return out[:-1].split("\0")
1809 return []
1810
1811 def DiffZ(self, name, *args):
1812 cmd = [name]
1813 cmd.append('-z')
1814 cmd.extend(args)
1815 p = GitCommand(self._project,
1816 cmd,
1817 bare = False,
1818 capture_stdout = True,
1819 capture_stderr = True)
1820 try:
1821 out = p.process.stdout.read()
1822 r = {}
1823 if out:
1824 out = iter(out[:-1].split('\0'))
1825 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001826 try:
1827 info = out.next()
1828 path = out.next()
1829 except StopIteration:
1830 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831
1832 class _Info(object):
1833 def __init__(self, path, omode, nmode, oid, nid, state):
1834 self.path = path
1835 self.src_path = None
1836 self.old_mode = omode
1837 self.new_mode = nmode
1838 self.old_id = oid
1839 self.new_id = nid
1840
1841 if len(state) == 1:
1842 self.status = state
1843 self.level = None
1844 else:
1845 self.status = state[:1]
1846 self.level = state[1:]
1847 while self.level.startswith('0'):
1848 self.level = self.level[1:]
1849
1850 info = info[1:].split(' ')
1851 info =_Info(path, *info)
1852 if info.status in ('R', 'C'):
1853 info.src_path = info.path
1854 info.path = out.next()
1855 r[info.path] = info
1856 return r
1857 finally:
1858 p.Wait()
1859
1860 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001861 if self._bare:
1862 path = os.path.join(self._project.gitdir, HEAD)
1863 else:
1864 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001865 fd = open(path, 'rb')
1866 try:
1867 line = fd.read()
1868 finally:
1869 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001870 if line.startswith('ref: '):
1871 return line[5:-1]
1872 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001873
1874 def SetHead(self, ref, message=None):
1875 cmdv = []
1876 if message is not None:
1877 cmdv.extend(['-m', message])
1878 cmdv.append(HEAD)
1879 cmdv.append(ref)
1880 self.symbolic_ref(*cmdv)
1881
1882 def DetachHead(self, new, message=None):
1883 cmdv = ['--no-deref']
1884 if message is not None:
1885 cmdv.extend(['-m', message])
1886 cmdv.append(HEAD)
1887 cmdv.append(new)
1888 self.update_ref(*cmdv)
1889
1890 def UpdateRef(self, name, new, old=None,
1891 message=None,
1892 detach=False):
1893 cmdv = []
1894 if message is not None:
1895 cmdv.extend(['-m', message])
1896 if detach:
1897 cmdv.append('--no-deref')
1898 cmdv.append(name)
1899 cmdv.append(new)
1900 if old is not None:
1901 cmdv.append(old)
1902 self.update_ref(*cmdv)
1903
1904 def DeleteRef(self, name, old=None):
1905 if not old:
1906 old = self.rev_parse(name)
1907 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001908 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001909
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001910 def rev_list(self, *args, **kw):
1911 if 'format' in kw:
1912 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1913 else:
1914 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001915 cmdv.extend(args)
1916 p = GitCommand(self._project,
1917 cmdv,
1918 bare = self._bare,
1919 capture_stdout = True,
1920 capture_stderr = True)
1921 r = []
1922 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001923 if line[-1] == '\n':
1924 line = line[:-1]
1925 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001926 if p.Wait() != 0:
1927 raise GitError('%s rev-list %s: %s' % (
1928 self._project.name,
1929 str(args),
1930 p.stderr))
1931 return r
1932
1933 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001934 """Allow arbitrary git commands using pythonic syntax.
1935
1936 This allows you to do things like:
1937 git_obj.rev_parse('HEAD')
1938
1939 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1940 run. We'll replace the '_' with a '-' and try to run a git command.
1941 Any other arguments will be passed to the git command.
1942
1943 Args:
1944 name: The name of the git command to call. Any '_' characters will
1945 be replaced with '-'.
1946
1947 Returns:
1948 A callable object that will try to call git with the named command.
1949 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001950 name = name.replace('_', '-')
1951 def runner(*args):
1952 cmdv = [name]
1953 cmdv.extend(args)
1954 p = GitCommand(self._project,
1955 cmdv,
1956 bare = self._bare,
1957 capture_stdout = True,
1958 capture_stderr = True)
1959 if p.Wait() != 0:
1960 raise GitError('%s %s: %s' % (
1961 self._project.name,
1962 name,
1963 p.stderr))
1964 r = p.stdout
1965 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1966 return r[:-1]
1967 return r
1968 return runner
1969
1970
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001971class _PriorSyncFailedError(Exception):
1972 def __str__(self):
1973 return 'prior sync failed; rebase still in progress'
1974
1975class _DirtyError(Exception):
1976 def __str__(self):
1977 return 'contains uncommitted changes'
1978
1979class _InfoMessage(object):
1980 def __init__(self, project, text):
1981 self.project = project
1982 self.text = text
1983
1984 def Print(self, syncbuf):
1985 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1986 syncbuf.out.nl()
1987
1988class _Failure(object):
1989 def __init__(self, project, why):
1990 self.project = project
1991 self.why = why
1992
1993 def Print(self, syncbuf):
1994 syncbuf.out.fail('error: %s/: %s',
1995 self.project.relpath,
1996 str(self.why))
1997 syncbuf.out.nl()
1998
1999class _Later(object):
2000 def __init__(self, project, action):
2001 self.project = project
2002 self.action = action
2003
2004 def Run(self, syncbuf):
2005 out = syncbuf.out
2006 out.project('project %s/', self.project.relpath)
2007 out.nl()
2008 try:
2009 self.action()
2010 out.nl()
2011 return True
2012 except GitError, e:
2013 out.nl()
2014 return False
2015
2016class _SyncColoring(Coloring):
2017 def __init__(self, config):
2018 Coloring.__init__(self, config, 'reposync')
2019 self.project = self.printer('header', attr = 'bold')
2020 self.info = self.printer('info')
2021 self.fail = self.printer('fail', fg='red')
2022
2023class SyncBuffer(object):
2024 def __init__(self, config, detach_head=False):
2025 self._messages = []
2026 self._failures = []
2027 self._later_queue1 = []
2028 self._later_queue2 = []
2029
2030 self.out = _SyncColoring(config)
2031 self.out.redirect(sys.stderr)
2032
2033 self.detach_head = detach_head
2034 self.clean = True
2035
2036 def info(self, project, fmt, *args):
2037 self._messages.append(_InfoMessage(project, fmt % args))
2038
2039 def fail(self, project, err=None):
2040 self._failures.append(_Failure(project, err))
2041 self.clean = False
2042
2043 def later1(self, project, what):
2044 self._later_queue1.append(_Later(project, what))
2045
2046 def later2(self, project, what):
2047 self._later_queue2.append(_Later(project, what))
2048
2049 def Finish(self):
2050 self._PrintMessages()
2051 self._RunLater()
2052 self._PrintMessages()
2053 return self.clean
2054
2055 def _RunLater(self):
2056 for q in ['_later_queue1', '_later_queue2']:
2057 if not self._RunQueue(q):
2058 return
2059
2060 def _RunQueue(self, queue):
2061 for m in getattr(self, queue):
2062 if not m.Run(self):
2063 self.clean = False
2064 return False
2065 setattr(self, queue, [])
2066 return True
2067
2068 def _PrintMessages(self):
2069 for m in self._messages:
2070 m.Print(self)
2071 for m in self._failures:
2072 m.Print(self)
2073
2074 self._messages = []
2075 self._failures = []
2076
2077
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002078class MetaProject(Project):
2079 """A special project housed under .repo.
2080 """
2081 def __init__(self, manifest, name, gitdir, worktree):
2082 repodir = manifest.repodir
2083 Project.__init__(self,
2084 manifest = manifest,
2085 name = name,
2086 gitdir = gitdir,
2087 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002088 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002090 revisionExpr = 'refs/heads/master',
2091 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002092
2093 def PreSync(self):
2094 if self.Exists:
2095 cb = self.CurrentBranch
2096 if cb:
2097 base = self.GetBranch(cb).merge
2098 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002099 self.revisionExpr = base
2100 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101
2102 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002103 def LastFetch(self):
2104 try:
2105 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2106 return os.path.getmtime(fh)
2107 except OSError:
2108 return 0
2109
2110 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111 def HasChanges(self):
2112 """Has the remote received new commits not yet checked out?
2113 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002114 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002115 return False
2116
2117 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002118 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002119 head = self.work_git.GetHead()
2120 if head.startswith(R_HEADS):
2121 try:
2122 head = all[head]
2123 except KeyError:
2124 head = None
2125
2126 if revid == head:
2127 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002128 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002129 return True
2130 return False