blob: 9b23b116152ba5be125be700616f45737a863652 [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)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800886
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800887 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)
893
894 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
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700905 def Sync_NetworkHalf(self, quiet=False, is_new=None, current_branch_only=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 """Perform only the network IO portion of the sync process.
907 Local working directory/branch state is not affected.
908 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700909 if is_new is None:
910 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200911 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 self._InitGitDir()
913 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700914
915 if is_new:
916 alt = os.path.join(self.gitdir, 'objects/info/alternates')
917 try:
918 fd = open(alt, 'rb')
919 try:
920 alt_dir = fd.readline().rstrip()
921 finally:
922 fd.close()
923 except IOError:
924 alt_dir = None
925 else:
926 alt_dir = None
927
928 if alt_dir is None and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
929 is_new = False
930
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700931 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
932 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800934
935 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800936 self._InitMRef()
937 else:
938 self._InitMirrorHead()
939 try:
940 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
941 except OSError:
942 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800944
945 def PostRepoUpgrade(self):
946 self._InitHooks()
947
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 def _CopyFiles(self):
949 for file in self.copyfiles:
950 file._Copy()
951
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700952 def GetRevisionId(self, all=None):
953 if self.revisionId:
954 return self.revisionId
955
956 rem = self.GetRemote(self.remote.name)
957 rev = rem.ToLocal(self.revisionExpr)
958
959 if all is not None and rev in all:
960 return all[rev]
961
962 try:
963 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
964 except GitError:
965 raise ManifestInvalidRevisionError(
966 'revision %s in %s not found' % (self.revisionExpr,
967 self.name))
968
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700969 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 """Perform only the local IO portion of the sync process.
971 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700973 all = self.bare_ref.all
974 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700975 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800976
977 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700978 head = self.work_git.GetHead()
979 if head.startswith(R_HEADS):
980 branch = head[len(R_HEADS):]
981 try:
982 head = all[head]
983 except KeyError:
984 head = None
985 else:
986 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700988 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 # Currently on a detached HEAD. The user is assumed to
990 # not have any local modifications worth worrying about.
991 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700992 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700993 syncbuf.fail(self, _PriorSyncFailedError())
994 return
995
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700996 if head == revid:
997 # No changes; don't do anything further.
998 #
999 return
1000
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001001 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001003 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001005 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001006 except GitError, e:
1007 syncbuf.fail(self, e)
1008 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001010 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001012 if head == revid:
1013 # No changes; don't do anything further.
1014 #
1015 return
1016
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001019 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001021 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001023 syncbuf.info(self,
1024 "leaving %s; does not track upstream",
1025 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001027 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001028 except GitError, e:
1029 syncbuf.fail(self, e)
1030 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001034 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001035 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001037 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 if not_merged:
1039 if upstream_gain:
1040 # The user has published this branch and some of those
1041 # commits are not yet merged upstream. We do not want
1042 # to rewrite the published commits so we punt.
1043 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001044 syncbuf.fail(self,
1045 "branch %s is published (but not merged) and is now %d commits behind"
1046 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001047 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001048 elif pub == head:
1049 # All published commits are merged, and thus we are a
1050 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001051 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001052 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001053 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001054 self._CopyFiles()
1055 syncbuf.later1(self, _doff)
1056 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001058 # Examine the local commits not in the remote. Find the
1059 # last one attributed to this user, if any.
1060 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001061 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001062 last_mine = None
1063 cnt_mine = 0
1064 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001065 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001066 if committer_email == self.UserEmail:
1067 last_mine = commit_id
1068 cnt_mine += 1
1069
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001070 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
1073 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001074 syncbuf.fail(self, _DirtyError())
1075 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001077 # If the upstream switched on us, warn the user.
1078 #
1079 if branch.merge != self.revisionExpr:
1080 if branch.merge and self.revisionExpr:
1081 syncbuf.info(self,
1082 'manifest switched %s...%s',
1083 branch.merge,
1084 self.revisionExpr)
1085 elif branch.merge:
1086 syncbuf.info(self,
1087 'manifest no longer tracks %s',
1088 branch.merge)
1089
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001090 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001092 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001094 syncbuf.info(self,
1095 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001096 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001098 branch.remote = self.GetRemote(self.remote.name)
1099 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 branch.Save()
1101
Mike Pontillod3153822012-02-28 11:53:24 -08001102 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001104 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001105 self._CopyFiles()
1106 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001107 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001109 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001110 self._CopyFiles()
1111 except GitError, e:
1112 syncbuf.fail(self, e)
1113 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001115 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001116 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001117 self._CopyFiles()
1118 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001120 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 # dest should already be an absolute path, but src is project relative
1122 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001123 abssrc = os.path.join(self.worktree, src)
1124 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001126 def DownloadPatchSet(self, change_id, patch_id):
1127 """Download a single patch set of a single change to FETCH_HEAD.
1128 """
1129 remote = self.GetRemote(self.remote.name)
1130
1131 cmd = ['fetch', remote.name]
1132 cmd.append('refs/changes/%2.2d/%d/%d' \
1133 % (change_id % 100, change_id, patch_id))
1134 cmd.extend(map(lambda x: str(x), remote.fetch))
1135 if GitCommand(self, cmd, bare=True).Wait() != 0:
1136 return None
1137 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001138 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001139 change_id,
1140 patch_id,
1141 self.bare_git.rev_parse('FETCH_HEAD'))
1142
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143
1144## Branch Management ##
1145
1146 def StartBranch(self, name):
1147 """Create a new branch off the manifest's revision.
1148 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001149 head = self.work_git.GetHead()
1150 if head == (R_HEADS + name):
1151 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001153 all = self.bare_ref.all
1154 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001155 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001156 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001157 capture_stdout = True,
1158 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001159
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001160 branch = self.GetBranch(name)
1161 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001162 branch.merge = self.revisionExpr
1163 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001164
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001165 if head.startswith(R_HEADS):
1166 try:
1167 head = all[head]
1168 except KeyError:
1169 head = None
1170
1171 if revid and head and revid == head:
1172 ref = os.path.join(self.gitdir, R_HEADS + name)
1173 try:
1174 os.makedirs(os.path.dirname(ref))
1175 except OSError:
1176 pass
1177 _lwrite(ref, '%s\n' % revid)
1178 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1179 'ref: %s%s\n' % (R_HEADS, name))
1180 branch.Save()
1181 return True
1182
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001183 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001184 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001185 capture_stdout = True,
1186 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001187 branch.Save()
1188 return True
1189 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
Wink Saville02d79452009-04-10 13:01:24 -07001191 def CheckoutBranch(self, name):
1192 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001193
1194 Args:
1195 name: The name of the branch to checkout.
1196
1197 Returns:
1198 True if the checkout succeeded; False if it didn't; None if the branch
1199 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001200 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001201 rev = R_HEADS + name
1202 head = self.work_git.GetHead()
1203 if head == rev:
1204 # Already on the branch
1205 #
1206 return True
Wink Saville02d79452009-04-10 13:01:24 -07001207
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001208 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001209 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001210 revid = all[rev]
1211 except KeyError:
1212 # Branch does not exist in this project
1213 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001214 return None
Wink Saville02d79452009-04-10 13:01:24 -07001215
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001216 if head.startswith(R_HEADS):
1217 try:
1218 head = all[head]
1219 except KeyError:
1220 head = None
1221
1222 if head == revid:
1223 # Same revision; just update HEAD to point to the new
1224 # target branch, but otherwise take no other action.
1225 #
1226 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1227 'ref: %s%s\n' % (R_HEADS, name))
1228 return True
1229
1230 return GitCommand(self,
1231 ['checkout', name, '--'],
1232 capture_stdout = True,
1233 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001234
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001235 def AbandonBranch(self, name):
1236 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001237
1238 Args:
1239 name: The name of the branch to abandon.
1240
1241 Returns:
1242 True if the abandon succeeded; False if it didn't; None if the branch
1243 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001244 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001245 rev = R_HEADS + name
1246 all = self.bare_ref.all
1247 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001248 # Doesn't exist
1249 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001250
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001251 head = self.work_git.GetHead()
1252 if head == rev:
1253 # We can't destroy the branch while we are sitting
1254 # on it. Switch to a detached HEAD.
1255 #
1256 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001257
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001258 revid = self.GetRevisionId(all)
1259 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001260 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1261 '%s\n' % revid)
1262 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001264
1265 return GitCommand(self,
1266 ['branch', '-D', name],
1267 capture_stdout = True,
1268 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001269
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001270 def PruneHeads(self):
1271 """Prune any topic branches already merged into upstream.
1272 """
1273 cb = self.CurrentBranch
1274 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001275 left = self._allrefs
1276 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277 if name.startswith(R_HEADS):
1278 name = name[len(R_HEADS):]
1279 if cb is None or name != cb:
1280 kill.append(name)
1281
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001282 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283 if cb is not None \
1284 and not self._revlist(HEAD + '...' + rev) \
1285 and not self.IsDirty(consider_untracked = False):
1286 self.work_git.DetachHead(HEAD)
1287 kill.append(cb)
1288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001290 old = self.bare_git.GetHead()
1291 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1293
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 try:
1295 self.bare_git.DetachHead(rev)
1296
1297 b = ['branch', '-d']
1298 b.extend(kill)
1299 b = GitCommand(self, b, bare=True,
1300 capture_stdout=True,
1301 capture_stderr=True)
1302 b.Wait()
1303 finally:
1304 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001305 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001307 for branch in kill:
1308 if (R_HEADS + branch) not in left:
1309 self.CleanPublishedCache()
1310 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311
1312 if cb and cb not in kill:
1313 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001314 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
1316 kept = []
1317 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001318 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 branch = self.GetBranch(branch)
1320 base = branch.LocalMerge
1321 if not base:
1322 base = rev
1323 kept.append(ReviewableBranch(self, branch, base))
1324 return kept
1325
1326
1327## Direct Git Commands ##
1328
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001329 def _RemoteFetch(self, name=None,
1330 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001331 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001332 quiet=False,
1333 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001334
1335 is_sha1 = False
1336 tag_name = None
1337
1338 if current_branch_only:
1339 if ID_RE.match(self.revisionExpr) is not None:
1340 is_sha1 = True
1341 elif self.revisionExpr.startswith(R_TAGS):
1342 # this is a tag and its sha1 value should never change
1343 tag_name = self.revisionExpr[len(R_TAGS):]
1344
1345 if is_sha1 or tag_name is not None:
1346 try:
1347 self.GetRevisionId()
1348 return True
1349 except ManifestInvalidRevisionError:
1350 # There is no such persistent revision. We have to fetch it.
1351 pass
1352
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 if not name:
1354 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001355
1356 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001357 remote = self.GetRemote(name)
1358 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001359 ssh_proxy = True
1360
Shawn O. Pearce88443382010-10-08 10:02:09 +02001361 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001362 if alt_dir and 'objects' == os.path.basename(alt_dir):
1363 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001364 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1365 remote = self.GetRemote(name)
1366
1367 all = self.bare_ref.all
1368 ids = set(all.values())
1369 tmp = set()
1370
1371 for r, id in GitRefs(ref_dir).all.iteritems():
1372 if r not in all:
1373 if r.startswith(R_TAGS) or remote.WritesTo(r):
1374 all[r] = id
1375 ids.add(id)
1376 continue
1377
1378 if id in ids:
1379 continue
1380
1381 r = 'refs/_alt/%s' % id
1382 all[r] = id
1383 ids.add(id)
1384 tmp.add(r)
1385
1386 ref_names = list(all.keys())
1387 ref_names.sort()
1388
1389 tmp_packed = ''
1390 old_packed = ''
1391
1392 for r in ref_names:
1393 line = '%s %s\n' % (all[r], r)
1394 tmp_packed += line
1395 if r not in tmp:
1396 old_packed += line
1397
1398 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001399 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001400 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001401
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001402 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001403
1404 # The --depth option only affects the initial fetch; after that we'll do
1405 # full fetches of changes.
1406 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1407 if depth and initial:
1408 cmd.append('--depth=%s' % depth)
1409
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001410 if quiet:
1411 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001412 if not self.worktree:
1413 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001414 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001415
1416 if not current_branch_only or is_sha1:
1417 # Fetch whole repo
1418 cmd.append('--tags')
1419 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1420 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001421 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001422 cmd.append(tag_name)
1423 else:
1424 branch = self.revisionExpr
1425 if branch.startswith(R_HEADS):
1426 branch = branch[len(R_HEADS):]
1427 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001428
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001429 ok = False
1430 for i in range(2):
1431 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1432 ok = True
1433 break
1434 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001435
1436 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001437 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001438 if old_packed != '':
1439 _lwrite(packed_refs, old_packed)
1440 else:
1441 os.remove(packed_refs)
1442 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001443 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001444
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001445 def _ApplyCloneBundle(self, initial=False, quiet=False):
1446 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1447 return False
1448
1449 remote = self.GetRemote(self.remote.name)
1450 bundle_url = remote.url + '/clone.bundle'
1451 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001452 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1453 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001454 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1455 return False
1456
1457 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1458 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1459
1460 exist_dst = os.path.exists(bundle_dst)
1461 exist_tmp = os.path.exists(bundle_tmp)
1462
1463 if not initial and not exist_dst and not exist_tmp:
1464 return False
1465
1466 if not exist_dst:
1467 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1468 if not exist_dst:
1469 return False
1470
1471 cmd = ['fetch']
1472 if quiet:
1473 cmd.append('--quiet')
1474 if not self.worktree:
1475 cmd.append('--update-head-ok')
1476 cmd.append(bundle_dst)
1477 for f in remote.fetch:
1478 cmd.append(str(f))
1479 cmd.append('refs/tags/*:refs/tags/*')
1480
1481 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001482 if os.path.exists(bundle_dst):
1483 os.remove(bundle_dst)
1484 if os.path.exists(bundle_tmp):
1485 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001486 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001488 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001489 keep = True
1490 done = False
1491 dest = open(tmpPath, 'a+b')
1492 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001493 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001494 pos = dest.tell()
1495
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001496 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001497 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001498 req = urllib2.Request(srcUrl)
1499 if pos > 0:
1500 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001501
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001502 try:
1503 r = urllib2.urlopen(req)
1504 except urllib2.HTTPError, e:
1505 def _content_type():
1506 try:
1507 return e.info()['content-type']
1508 except:
1509 return None
1510
1511 if e.code == 404:
1512 keep = False
1513 return False
1514 elif _content_type() == 'text/plain':
1515 try:
1516 msg = e.read()
1517 if len(msg) > 0 and msg[-1] == '\n':
1518 msg = msg[0:-1]
1519 msg = ' (%s)' % msg
1520 except:
1521 msg = ''
1522 else:
1523 try:
1524 from BaseHTTPServer import BaseHTTPRequestHandler
1525 res = BaseHTTPRequestHandler.responses[e.code]
1526 msg = ' (%s: %s)' % (res[0], res[1])
1527 except:
1528 msg = ''
1529 raise DownloadError('HTTP %s%s' % (e.code, msg))
1530 except urllib2.URLError, e:
1531 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1532 finally:
1533 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001534
1535 p = None
1536 try:
Conley Owens43bda842012-03-12 11:25:04 -07001537 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001538 unit = 1 << 10
1539
1540 if size and not quiet:
1541 if size > 1024 * 1.3:
1542 unit = 1 << 20
1543 desc = 'MB'
1544 else:
1545 desc = 'KB'
1546 p = Progress(
1547 'Downloading %s' % self.relpath,
1548 int(size) / unit,
1549 units=desc)
1550 if pos > 0:
1551 p.update(pos / unit)
1552
1553 s = 0
1554 while True:
1555 d = r.read(8192)
1556 if d == '':
1557 done = True
1558 return True
1559 dest.write(d)
1560 if p:
1561 s += len(d)
1562 if s >= unit:
1563 p.update(s / unit)
1564 s = s % unit
1565 if p:
1566 if s >= unit:
1567 p.update(s / unit)
1568 else:
1569 p.update(1)
1570 finally:
1571 r.close()
1572 if p:
1573 p.end()
1574 finally:
1575 dest.close()
1576
1577 if os.path.exists(dstPath):
1578 os.remove(dstPath)
1579 if done:
1580 os.rename(tmpPath, dstPath)
1581 elif not keep:
1582 os.remove(tmpPath)
1583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584 def _Checkout(self, rev, quiet=False):
1585 cmd = ['checkout']
1586 if quiet:
1587 cmd.append('-q')
1588 cmd.append(rev)
1589 cmd.append('--')
1590 if GitCommand(self, cmd).Wait() != 0:
1591 if self._allrefs:
1592 raise GitError('%s checkout %s ' % (self.name, rev))
1593
1594 def _ResetHard(self, rev, quiet=True):
1595 cmd = ['reset', '--hard']
1596 if quiet:
1597 cmd.append('-q')
1598 cmd.append(rev)
1599 if GitCommand(self, cmd).Wait() != 0:
1600 raise GitError('%s reset --hard %s ' % (self.name, rev))
1601
1602 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001603 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 if onto is not None:
1605 cmd.extend(['--onto', onto])
1606 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001607 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001608 raise GitError('%s rebase %s ' % (self.name, upstream))
1609
1610 def _FastForward(self, head):
1611 cmd = ['merge', head]
1612 if GitCommand(self, cmd).Wait() != 0:
1613 raise GitError('%s merge %s ' % (self.name, head))
1614
1615 def _InitGitDir(self):
1616 if not os.path.exists(self.gitdir):
1617 os.makedirs(self.gitdir)
1618 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001619
Shawn O. Pearce88443382010-10-08 10:02:09 +02001620 mp = self.manifest.manifestProject
1621 ref_dir = mp.config.GetString('repo.reference')
1622
1623 if ref_dir:
1624 mirror_git = os.path.join(ref_dir, self.name + '.git')
1625 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1626 self.relpath + '.git')
1627
1628 if os.path.exists(mirror_git):
1629 ref_dir = mirror_git
1630
1631 elif os.path.exists(repo_git):
1632 ref_dir = repo_git
1633
1634 else:
1635 ref_dir = None
1636
1637 if ref_dir:
1638 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1639 os.path.join(ref_dir, 'objects') + '\n')
1640
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001641 if self.manifest.IsMirror:
1642 self.config.SetString('core.bare', 'true')
1643 else:
1644 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645
1646 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001647 try:
1648 to_rm = os.listdir(hooks)
1649 except OSError:
1650 to_rm = []
1651 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001653 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654
1655 m = self.manifest.manifestProject.config
1656 for key in ['user.name', 'user.email']:
1657 if m.Has(key, include_defaults = False):
1658 self.config.SetString(key, m.GetString(key))
1659
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001660 def _InitHooks(self):
1661 hooks = self._gitdir_path('hooks')
1662 if not os.path.exists(hooks):
1663 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001664 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001665 name = os.path.basename(stock_hook)
1666
Victor Boivie65e0f352011-04-18 11:23:29 +02001667 if name in ('commit-msg',) and not self.remote.review \
1668 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001669 # Don't install a Gerrit Code Review hook if this
1670 # project does not appear to use it for reviews.
1671 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001672 # Since the manifest project is one of those, but also
1673 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001674 continue
1675
1676 dst = os.path.join(hooks, name)
1677 if os.path.islink(dst):
1678 continue
1679 if os.path.exists(dst):
1680 if filecmp.cmp(stock_hook, dst, shallow=False):
1681 os.remove(dst)
1682 else:
1683 _error("%s: Not replacing %s hook", self.relpath, name)
1684 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001685 try:
1686 os.symlink(relpath(stock_hook, dst), dst)
1687 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001688 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001689 raise GitError('filesystem must support symlinks')
1690 else:
1691 raise
1692
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001694 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001696 remote.url = self.remote.url
1697 remote.review = self.remote.review
1698 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001700 if self.worktree:
1701 remote.ResetFetch(mirror=False)
1702 else:
1703 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 remote.Save()
1705
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 def _InitMRef(self):
1707 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001708 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001709
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001710 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001711 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001712
1713 def _InitAnyMRef(self, ref):
1714 cur = self.bare_ref.symref(ref)
1715
1716 if self.revisionId:
1717 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1718 msg = 'manifest set to %s' % self.revisionId
1719 dst = self.revisionId + '^0'
1720 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1721 else:
1722 remote = self.GetRemote(self.remote.name)
1723 dst = remote.ToLocal(self.revisionExpr)
1724 if cur != dst:
1725 msg = 'manifest set to %s' % self.revisionExpr
1726 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 def _InitWorkTree(self):
1729 dotgit = os.path.join(self.worktree, '.git')
1730 if not os.path.exists(dotgit):
1731 os.makedirs(dotgit)
1732
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733 for name in ['config',
1734 'description',
1735 'hooks',
1736 'info',
1737 'logs',
1738 'objects',
1739 'packed-refs',
1740 'refs',
1741 'rr-cache',
1742 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001743 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001744 src = os.path.join(self.gitdir, name)
1745 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001746 if os.path.islink(dst) or not os.path.exists(dst):
1747 os.symlink(relpath(src, dst), dst)
1748 else:
1749 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001750 except OSError, e:
1751 if e.errno == errno.EPERM:
1752 raise GitError('filesystem must support symlinks')
1753 else:
1754 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001756 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757
1758 cmd = ['read-tree', '--reset', '-u']
1759 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001760 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001761 if GitCommand(self, cmd).Wait() != 0:
1762 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001763
1764 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1765 if not os.path.exists(rr_cache):
1766 os.makedirs(rr_cache)
1767
Shawn O. Pearce93609662009-04-21 10:50:33 -07001768 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769
1770 def _gitdir_path(self, path):
1771 return os.path.join(self.gitdir, path)
1772
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001773 def _revlist(self, *args, **kw):
1774 a = []
1775 a.extend(args)
1776 a.append('--')
1777 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778
1779 @property
1780 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001781 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782
1783 class _GitGetByExec(object):
1784 def __init__(self, project, bare):
1785 self._project = project
1786 self._bare = bare
1787
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001788 def LsOthers(self):
1789 p = GitCommand(self._project,
1790 ['ls-files',
1791 '-z',
1792 '--others',
1793 '--exclude-standard'],
1794 bare = False,
1795 capture_stdout = True,
1796 capture_stderr = True)
1797 if p.Wait() == 0:
1798 out = p.stdout
1799 if out:
1800 return out[:-1].split("\0")
1801 return []
1802
1803 def DiffZ(self, name, *args):
1804 cmd = [name]
1805 cmd.append('-z')
1806 cmd.extend(args)
1807 p = GitCommand(self._project,
1808 cmd,
1809 bare = False,
1810 capture_stdout = True,
1811 capture_stderr = True)
1812 try:
1813 out = p.process.stdout.read()
1814 r = {}
1815 if out:
1816 out = iter(out[:-1].split('\0'))
1817 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001818 try:
1819 info = out.next()
1820 path = out.next()
1821 except StopIteration:
1822 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001823
1824 class _Info(object):
1825 def __init__(self, path, omode, nmode, oid, nid, state):
1826 self.path = path
1827 self.src_path = None
1828 self.old_mode = omode
1829 self.new_mode = nmode
1830 self.old_id = oid
1831 self.new_id = nid
1832
1833 if len(state) == 1:
1834 self.status = state
1835 self.level = None
1836 else:
1837 self.status = state[:1]
1838 self.level = state[1:]
1839 while self.level.startswith('0'):
1840 self.level = self.level[1:]
1841
1842 info = info[1:].split(' ')
1843 info =_Info(path, *info)
1844 if info.status in ('R', 'C'):
1845 info.src_path = info.path
1846 info.path = out.next()
1847 r[info.path] = info
1848 return r
1849 finally:
1850 p.Wait()
1851
1852 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001853 if self._bare:
1854 path = os.path.join(self._project.gitdir, HEAD)
1855 else:
1856 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001857 fd = open(path, 'rb')
1858 try:
1859 line = fd.read()
1860 finally:
1861 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001862 if line.startswith('ref: '):
1863 return line[5:-1]
1864 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001865
1866 def SetHead(self, ref, message=None):
1867 cmdv = []
1868 if message is not None:
1869 cmdv.extend(['-m', message])
1870 cmdv.append(HEAD)
1871 cmdv.append(ref)
1872 self.symbolic_ref(*cmdv)
1873
1874 def DetachHead(self, new, message=None):
1875 cmdv = ['--no-deref']
1876 if message is not None:
1877 cmdv.extend(['-m', message])
1878 cmdv.append(HEAD)
1879 cmdv.append(new)
1880 self.update_ref(*cmdv)
1881
1882 def UpdateRef(self, name, new, old=None,
1883 message=None,
1884 detach=False):
1885 cmdv = []
1886 if message is not None:
1887 cmdv.extend(['-m', message])
1888 if detach:
1889 cmdv.append('--no-deref')
1890 cmdv.append(name)
1891 cmdv.append(new)
1892 if old is not None:
1893 cmdv.append(old)
1894 self.update_ref(*cmdv)
1895
1896 def DeleteRef(self, name, old=None):
1897 if not old:
1898 old = self.rev_parse(name)
1899 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001900 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001901
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001902 def rev_list(self, *args, **kw):
1903 if 'format' in kw:
1904 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1905 else:
1906 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001907 cmdv.extend(args)
1908 p = GitCommand(self._project,
1909 cmdv,
1910 bare = self._bare,
1911 capture_stdout = True,
1912 capture_stderr = True)
1913 r = []
1914 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001915 if line[-1] == '\n':
1916 line = line[:-1]
1917 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001918 if p.Wait() != 0:
1919 raise GitError('%s rev-list %s: %s' % (
1920 self._project.name,
1921 str(args),
1922 p.stderr))
1923 return r
1924
1925 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001926 """Allow arbitrary git commands using pythonic syntax.
1927
1928 This allows you to do things like:
1929 git_obj.rev_parse('HEAD')
1930
1931 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1932 run. We'll replace the '_' with a '-' and try to run a git command.
1933 Any other arguments will be passed to the git command.
1934
1935 Args:
1936 name: The name of the git command to call. Any '_' characters will
1937 be replaced with '-'.
1938
1939 Returns:
1940 A callable object that will try to call git with the named command.
1941 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001942 name = name.replace('_', '-')
1943 def runner(*args):
1944 cmdv = [name]
1945 cmdv.extend(args)
1946 p = GitCommand(self._project,
1947 cmdv,
1948 bare = self._bare,
1949 capture_stdout = True,
1950 capture_stderr = True)
1951 if p.Wait() != 0:
1952 raise GitError('%s %s: %s' % (
1953 self._project.name,
1954 name,
1955 p.stderr))
1956 r = p.stdout
1957 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1958 return r[:-1]
1959 return r
1960 return runner
1961
1962
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001963class _PriorSyncFailedError(Exception):
1964 def __str__(self):
1965 return 'prior sync failed; rebase still in progress'
1966
1967class _DirtyError(Exception):
1968 def __str__(self):
1969 return 'contains uncommitted changes'
1970
1971class _InfoMessage(object):
1972 def __init__(self, project, text):
1973 self.project = project
1974 self.text = text
1975
1976 def Print(self, syncbuf):
1977 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1978 syncbuf.out.nl()
1979
1980class _Failure(object):
1981 def __init__(self, project, why):
1982 self.project = project
1983 self.why = why
1984
1985 def Print(self, syncbuf):
1986 syncbuf.out.fail('error: %s/: %s',
1987 self.project.relpath,
1988 str(self.why))
1989 syncbuf.out.nl()
1990
1991class _Later(object):
1992 def __init__(self, project, action):
1993 self.project = project
1994 self.action = action
1995
1996 def Run(self, syncbuf):
1997 out = syncbuf.out
1998 out.project('project %s/', self.project.relpath)
1999 out.nl()
2000 try:
2001 self.action()
2002 out.nl()
2003 return True
2004 except GitError, e:
2005 out.nl()
2006 return False
2007
2008class _SyncColoring(Coloring):
2009 def __init__(self, config):
2010 Coloring.__init__(self, config, 'reposync')
2011 self.project = self.printer('header', attr = 'bold')
2012 self.info = self.printer('info')
2013 self.fail = self.printer('fail', fg='red')
2014
2015class SyncBuffer(object):
2016 def __init__(self, config, detach_head=False):
2017 self._messages = []
2018 self._failures = []
2019 self._later_queue1 = []
2020 self._later_queue2 = []
2021
2022 self.out = _SyncColoring(config)
2023 self.out.redirect(sys.stderr)
2024
2025 self.detach_head = detach_head
2026 self.clean = True
2027
2028 def info(self, project, fmt, *args):
2029 self._messages.append(_InfoMessage(project, fmt % args))
2030
2031 def fail(self, project, err=None):
2032 self._failures.append(_Failure(project, err))
2033 self.clean = False
2034
2035 def later1(self, project, what):
2036 self._later_queue1.append(_Later(project, what))
2037
2038 def later2(self, project, what):
2039 self._later_queue2.append(_Later(project, what))
2040
2041 def Finish(self):
2042 self._PrintMessages()
2043 self._RunLater()
2044 self._PrintMessages()
2045 return self.clean
2046
2047 def _RunLater(self):
2048 for q in ['_later_queue1', '_later_queue2']:
2049 if not self._RunQueue(q):
2050 return
2051
2052 def _RunQueue(self, queue):
2053 for m in getattr(self, queue):
2054 if not m.Run(self):
2055 self.clean = False
2056 return False
2057 setattr(self, queue, [])
2058 return True
2059
2060 def _PrintMessages(self):
2061 for m in self._messages:
2062 m.Print(self)
2063 for m in self._failures:
2064 m.Print(self)
2065
2066 self._messages = []
2067 self._failures = []
2068
2069
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002070class MetaProject(Project):
2071 """A special project housed under .repo.
2072 """
2073 def __init__(self, manifest, name, gitdir, worktree):
2074 repodir = manifest.repodir
2075 Project.__init__(self,
2076 manifest = manifest,
2077 name = name,
2078 gitdir = gitdir,
2079 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002080 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002082 revisionExpr = 'refs/heads/master',
2083 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084
2085 def PreSync(self):
2086 if self.Exists:
2087 cb = self.CurrentBranch
2088 if cb:
2089 base = self.GetBranch(cb).merge
2090 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002091 self.revisionExpr = base
2092 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093
2094 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002095 def LastFetch(self):
2096 try:
2097 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2098 return os.path.getmtime(fh)
2099 except OSError:
2100 return 0
2101
2102 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103 def HasChanges(self):
2104 """Has the remote received new commits not yet checked out?
2105 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002106 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002107 return False
2108
2109 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002110 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002111 head = self.work_git.GetHead()
2112 if head.startswith(R_HEADS):
2113 try:
2114 head = all[head]
2115 except KeyError:
2116 head = None
2117
2118 if revid == head:
2119 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002120 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121 return True
2122 return False