blob: 303abe3336f36085ae1eac78a6cce52112d39812 [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
pelyad67872d2012-03-28 14:49:58 +0300752 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753 """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)
pelyad67872d2012-03-28 14:49:58 +0300760 if absolute_paths:
761 cmd.append('--src-prefix=a/%s/' % self.relpath)
762 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 cmd.append('--')
764 p = GitCommand(self,
765 cmd,
766 capture_stdout = True,
767 capture_stderr = True)
768 has_diff = False
769 for line in p.process.stdout:
770 if not has_diff:
771 out.nl()
772 out.project('project %s/' % self.relpath)
773 out.nl()
774 has_diff = True
775 print line[:-1]
776 p.Wait()
777
778
779## Publish / Upload ##
780
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700781 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 """Was the branch published (uploaded) for code review?
783 If so, returns the SHA-1 hash of the last published
784 state for the branch.
785 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700786 key = R_PUB + branch
787 if all is None:
788 try:
789 return self.bare_git.rev_parse(key)
790 except GitError:
791 return None
792 else:
793 try:
794 return all[key]
795 except KeyError:
796 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700798 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 """Prunes any stale published refs.
800 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700801 if all is None:
802 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 heads = set()
804 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700805 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806 if name.startswith(R_HEADS):
807 heads.add(name)
808 elif name.startswith(R_PUB):
809 canrm[name] = id
810
811 for name, id in canrm.iteritems():
812 n = name[len(R_PUB):]
813 if R_HEADS + n not in heads:
814 self.bare_git.DeleteRef(name, id)
815
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700816 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 """List any branches which can be uploaded for review.
818 """
819 heads = {}
820 pubed = {}
821
822 for name, id in self._allrefs.iteritems():
823 if name.startswith(R_HEADS):
824 heads[name[len(R_HEADS):]] = id
825 elif name.startswith(R_PUB):
826 pubed[name[len(R_PUB):]] = id
827
828 ready = []
829 for branch, id in heads.iteritems():
830 if branch in pubed and pubed[branch] == id:
831 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700832 if selected_branch and branch != selected_branch:
833 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800835 rb = self.GetUploadableBranch(branch)
836 if rb:
837 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 return ready
839
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800840 def GetUploadableBranch(self, branch_name):
841 """Get a single uploadable branch, or None.
842 """
843 branch = self.GetBranch(branch_name)
844 base = branch.LocalMerge
845 if branch.LocalMerge:
846 rb = ReviewableBranch(self, branch, base)
847 if rb.commits:
848 return rb
849 return None
850
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700851 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700852 people=([],[]),
853 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 """Uploads the named branch for code review.
855 """
856 if branch is None:
857 branch = self.CurrentBranch
858 if branch is None:
859 raise GitError('not currently on a branch')
860
861 branch = self.GetBranch(branch)
862 if not branch.LocalMerge:
863 raise GitError('branch %s does not track a remote' % branch.name)
864 if not branch.remote.review:
865 raise GitError('remote %s has no review url' % branch.remote.name)
866
867 dest_branch = branch.merge
868 if not dest_branch.startswith(R_HEADS):
869 dest_branch = R_HEADS + dest_branch
870
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800871 if not branch.remote.projectname:
872 branch.remote.projectname = self.name
873 branch.remote.Save()
874
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800875 url = branch.remote.ReviewUrl(self.UserEmail)
876 if url is None:
877 raise UploadError('review not configured')
878 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800879
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800880 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800881 rp = ['gerrit receive-pack']
882 for e in people[0]:
883 rp.append('--reviewer=%s' % sq(e))
884 for e in people[1]:
885 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800886 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700887
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800888 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800889
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800890 if dest_branch.startswith(R_HEADS):
891 dest_branch = dest_branch[len(R_HEADS):]
892 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
893 if auto_topic:
894 ref_spec = ref_spec + '/' + branch.name
895 cmd.append(ref_spec)
896
897 if GitCommand(self, cmd, bare = True).Wait() != 0:
898 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
900 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
901 self.bare_git.UpdateRef(R_PUB + branch.name,
902 R_HEADS + branch.name,
903 message = msg)
904
905
906## Sync ##
907
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700908 def Sync_NetworkHalf(self,
909 quiet=False,
910 is_new=None,
911 current_branch_only=False,
912 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 """Perform only the network IO portion of the sync process.
914 Local working directory/branch state is not affected.
915 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700916 if is_new is None:
917 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200918 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 self._InitGitDir()
920 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700921
922 if is_new:
923 alt = os.path.join(self.gitdir, 'objects/info/alternates')
924 try:
925 fd = open(alt, 'rb')
926 try:
927 alt_dir = fd.readline().rstrip()
928 finally:
929 fd.close()
930 except IOError:
931 alt_dir = None
932 else:
933 alt_dir = None
934
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700935 if clone_bundle \
936 and alt_dir is None \
937 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700938 is_new = False
939
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700940 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
941 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800943
944 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800945 self._InitMRef()
946 else:
947 self._InitMirrorHead()
948 try:
949 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
950 except OSError:
951 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800953
954 def PostRepoUpgrade(self):
955 self._InitHooks()
956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 def _CopyFiles(self):
958 for file in self.copyfiles:
959 file._Copy()
960
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700961 def GetRevisionId(self, all=None):
962 if self.revisionId:
963 return self.revisionId
964
965 rem = self.GetRemote(self.remote.name)
966 rev = rem.ToLocal(self.revisionExpr)
967
968 if all is not None and rev in all:
969 return all[rev]
970
971 try:
972 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
973 except GitError:
974 raise ManifestInvalidRevisionError(
975 'revision %s in %s not found' % (self.revisionExpr,
976 self.name))
977
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700978 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 """Perform only the local IO portion of the sync process.
980 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700982 all = self.bare_ref.all
983 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700984 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800985
986 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700987 head = self.work_git.GetHead()
988 if head.startswith(R_HEADS):
989 branch = head[len(R_HEADS):]
990 try:
991 head = all[head]
992 except KeyError:
993 head = None
994 else:
995 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700997 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 # Currently on a detached HEAD. The user is assumed to
999 # not have any local modifications worth worrying about.
1000 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001001 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001002 syncbuf.fail(self, _PriorSyncFailedError())
1003 return
1004
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001005 if head == revid:
1006 # No changes; don't do anything further.
1007 #
1008 return
1009
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001010 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001012 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001014 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001015 except GitError, e:
1016 syncbuf.fail(self, e)
1017 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001019 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001021 if head == revid:
1022 # No changes; don't do anything further.
1023 #
1024 return
1025
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001028 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001030 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 syncbuf.info(self,
1033 "leaving %s; does not track upstream",
1034 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001036 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001037 except GitError, e:
1038 syncbuf.fail(self, e)
1039 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001041 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001043 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001044 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001046 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047 if not_merged:
1048 if upstream_gain:
1049 # The user has published this branch and some of those
1050 # commits are not yet merged upstream. We do not want
1051 # to rewrite the published commits so we punt.
1052 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001053 syncbuf.fail(self,
1054 "branch %s is published (but not merged) and is now %d commits behind"
1055 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001057 elif pub == head:
1058 # All published commits are merged, and thus we are a
1059 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001060 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001061 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001062 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001063 self._CopyFiles()
1064 syncbuf.later1(self, _doff)
1065 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001067 # Examine the local commits not in the remote. Find the
1068 # last one attributed to this user, if any.
1069 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001070 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001071 last_mine = None
1072 cnt_mine = 0
1073 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001074 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001075 if committer_email == self.UserEmail:
1076 last_mine = commit_id
1077 cnt_mine += 1
1078
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001079 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001083 syncbuf.fail(self, _DirtyError())
1084 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 # If the upstream switched on us, warn the user.
1087 #
1088 if branch.merge != self.revisionExpr:
1089 if branch.merge and self.revisionExpr:
1090 syncbuf.info(self,
1091 'manifest switched %s...%s',
1092 branch.merge,
1093 self.revisionExpr)
1094 elif branch.merge:
1095 syncbuf.info(self,
1096 'manifest no longer tracks %s',
1097 branch.merge)
1098
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001101 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 syncbuf.info(self,
1104 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001107 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001108 if not ID_RE.match(self.revisionExpr):
1109 # in case of manifest sync the revisionExpr might be a SHA1
1110 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 branch.Save()
1112
Mike Pontillod3153822012-02-28 11:53:24 -08001113 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001115 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001116 self._CopyFiles()
1117 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001118 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001120 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 self._CopyFiles()
1122 except GitError, e:
1123 syncbuf.fail(self, e)
1124 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001127 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001128 self._CopyFiles()
1129 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001131 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 # dest should already be an absolute path, but src is project relative
1133 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001134 abssrc = os.path.join(self.worktree, src)
1135 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001137 def DownloadPatchSet(self, change_id, patch_id):
1138 """Download a single patch set of a single change to FETCH_HEAD.
1139 """
1140 remote = self.GetRemote(self.remote.name)
1141
1142 cmd = ['fetch', remote.name]
1143 cmd.append('refs/changes/%2.2d/%d/%d' \
1144 % (change_id % 100, change_id, patch_id))
1145 cmd.extend(map(lambda x: str(x), remote.fetch))
1146 if GitCommand(self, cmd, bare=True).Wait() != 0:
1147 return None
1148 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001149 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001150 change_id,
1151 patch_id,
1152 self.bare_git.rev_parse('FETCH_HEAD'))
1153
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154
1155## Branch Management ##
1156
1157 def StartBranch(self, name):
1158 """Create a new branch off the manifest's revision.
1159 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001160 head = self.work_git.GetHead()
1161 if head == (R_HEADS + name):
1162 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001164 all = self.bare_ref.all
1165 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001166 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001167 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001168 capture_stdout = True,
1169 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001170
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001171 branch = self.GetBranch(name)
1172 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001173 branch.merge = self.revisionExpr
1174 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001175
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001176 if head.startswith(R_HEADS):
1177 try:
1178 head = all[head]
1179 except KeyError:
1180 head = None
1181
1182 if revid and head and revid == head:
1183 ref = os.path.join(self.gitdir, R_HEADS + name)
1184 try:
1185 os.makedirs(os.path.dirname(ref))
1186 except OSError:
1187 pass
1188 _lwrite(ref, '%s\n' % revid)
1189 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1190 'ref: %s%s\n' % (R_HEADS, name))
1191 branch.Save()
1192 return True
1193
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001194 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001195 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001196 capture_stdout = True,
1197 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001198 branch.Save()
1199 return True
1200 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
Wink Saville02d79452009-04-10 13:01:24 -07001202 def CheckoutBranch(self, name):
1203 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001204
1205 Args:
1206 name: The name of the branch to checkout.
1207
1208 Returns:
1209 True if the checkout succeeded; False if it didn't; None if the branch
1210 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001211 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001212 rev = R_HEADS + name
1213 head = self.work_git.GetHead()
1214 if head == rev:
1215 # Already on the branch
1216 #
1217 return True
Wink Saville02d79452009-04-10 13:01:24 -07001218
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001219 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001220 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001221 revid = all[rev]
1222 except KeyError:
1223 # Branch does not exist in this project
1224 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001225 return None
Wink Saville02d79452009-04-10 13:01:24 -07001226
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001227 if head.startswith(R_HEADS):
1228 try:
1229 head = all[head]
1230 except KeyError:
1231 head = None
1232
1233 if head == revid:
1234 # Same revision; just update HEAD to point to the new
1235 # target branch, but otherwise take no other action.
1236 #
1237 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1238 'ref: %s%s\n' % (R_HEADS, name))
1239 return True
1240
1241 return GitCommand(self,
1242 ['checkout', name, '--'],
1243 capture_stdout = True,
1244 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001245
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001246 def AbandonBranch(self, name):
1247 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001248
1249 Args:
1250 name: The name of the branch to abandon.
1251
1252 Returns:
1253 True if the abandon succeeded; False if it didn't; None if the branch
1254 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001255 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001256 rev = R_HEADS + name
1257 all = self.bare_ref.all
1258 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001259 # Doesn't exist
1260 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001261
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001262 head = self.work_git.GetHead()
1263 if head == rev:
1264 # We can't destroy the branch while we are sitting
1265 # on it. Switch to a detached HEAD.
1266 #
1267 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001268
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001269 revid = self.GetRevisionId(all)
1270 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001271 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1272 '%s\n' % revid)
1273 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001274 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001275
1276 return GitCommand(self,
1277 ['branch', '-D', name],
1278 capture_stdout = True,
1279 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001280
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 def PruneHeads(self):
1282 """Prune any topic branches already merged into upstream.
1283 """
1284 cb = self.CurrentBranch
1285 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001286 left = self._allrefs
1287 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288 if name.startswith(R_HEADS):
1289 name = name[len(R_HEADS):]
1290 if cb is None or name != cb:
1291 kill.append(name)
1292
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001293 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 if cb is not None \
1295 and not self._revlist(HEAD + '...' + rev) \
1296 and not self.IsDirty(consider_untracked = False):
1297 self.work_git.DetachHead(HEAD)
1298 kill.append(cb)
1299
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001301 old = self.bare_git.GetHead()
1302 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1304
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 try:
1306 self.bare_git.DetachHead(rev)
1307
1308 b = ['branch', '-d']
1309 b.extend(kill)
1310 b = GitCommand(self, b, bare=True,
1311 capture_stdout=True,
1312 capture_stderr=True)
1313 b.Wait()
1314 finally:
1315 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001316 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001318 for branch in kill:
1319 if (R_HEADS + branch) not in left:
1320 self.CleanPublishedCache()
1321 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
1323 if cb and cb not in kill:
1324 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001325 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326
1327 kept = []
1328 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001329 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 branch = self.GetBranch(branch)
1331 base = branch.LocalMerge
1332 if not base:
1333 base = rev
1334 kept.append(ReviewableBranch(self, branch, base))
1335 return kept
1336
1337
1338## Direct Git Commands ##
1339
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001340 def _RemoteFetch(self, name=None,
1341 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001342 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001343 quiet=False,
1344 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001345
1346 is_sha1 = False
1347 tag_name = None
1348
1349 if current_branch_only:
1350 if ID_RE.match(self.revisionExpr) is not None:
1351 is_sha1 = True
1352 elif self.revisionExpr.startswith(R_TAGS):
1353 # this is a tag and its sha1 value should never change
1354 tag_name = self.revisionExpr[len(R_TAGS):]
1355
1356 if is_sha1 or tag_name is not None:
1357 try:
1358 self.GetRevisionId()
1359 return True
1360 except ManifestInvalidRevisionError:
1361 # There is no such persistent revision. We have to fetch it.
1362 pass
1363
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 if not name:
1365 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001366
1367 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001368 remote = self.GetRemote(name)
1369 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001370 ssh_proxy = True
1371
Shawn O. Pearce88443382010-10-08 10:02:09 +02001372 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001373 if alt_dir and 'objects' == os.path.basename(alt_dir):
1374 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001375 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1376 remote = self.GetRemote(name)
1377
1378 all = self.bare_ref.all
1379 ids = set(all.values())
1380 tmp = set()
1381
1382 for r, id in GitRefs(ref_dir).all.iteritems():
1383 if r not in all:
1384 if r.startswith(R_TAGS) or remote.WritesTo(r):
1385 all[r] = id
1386 ids.add(id)
1387 continue
1388
1389 if id in ids:
1390 continue
1391
1392 r = 'refs/_alt/%s' % id
1393 all[r] = id
1394 ids.add(id)
1395 tmp.add(r)
1396
1397 ref_names = list(all.keys())
1398 ref_names.sort()
1399
1400 tmp_packed = ''
1401 old_packed = ''
1402
1403 for r in ref_names:
1404 line = '%s %s\n' % (all[r], r)
1405 tmp_packed += line
1406 if r not in tmp:
1407 old_packed += line
1408
1409 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001410 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001411 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001412
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001413 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001414
1415 # The --depth option only affects the initial fetch; after that we'll do
1416 # full fetches of changes.
1417 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1418 if depth and initial:
1419 cmd.append('--depth=%s' % depth)
1420
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001421 if quiet:
1422 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001423 if not self.worktree:
1424 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001425 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001426
1427 if not current_branch_only or is_sha1:
1428 # Fetch whole repo
1429 cmd.append('--tags')
1430 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1431 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001432 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001433 cmd.append(tag_name)
1434 else:
1435 branch = self.revisionExpr
1436 if branch.startswith(R_HEADS):
1437 branch = branch[len(R_HEADS):]
1438 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001439
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001440 ok = False
1441 for i in range(2):
1442 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1443 ok = True
1444 break
1445 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001446
1447 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001448 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001449 if old_packed != '':
1450 _lwrite(packed_refs, old_packed)
1451 else:
1452 os.remove(packed_refs)
1453 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001454 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001455
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001456 def _ApplyCloneBundle(self, initial=False, quiet=False):
1457 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1458 return False
1459
1460 remote = self.GetRemote(self.remote.name)
1461 bundle_url = remote.url + '/clone.bundle'
1462 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001463 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1464 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001465 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1466 return False
1467
1468 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1469 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1470
1471 exist_dst = os.path.exists(bundle_dst)
1472 exist_tmp = os.path.exists(bundle_tmp)
1473
1474 if not initial and not exist_dst and not exist_tmp:
1475 return False
1476
1477 if not exist_dst:
1478 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1479 if not exist_dst:
1480 return False
1481
1482 cmd = ['fetch']
1483 if quiet:
1484 cmd.append('--quiet')
1485 if not self.worktree:
1486 cmd.append('--update-head-ok')
1487 cmd.append(bundle_dst)
1488 for f in remote.fetch:
1489 cmd.append(str(f))
1490 cmd.append('refs/tags/*:refs/tags/*')
1491
1492 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001493 if os.path.exists(bundle_dst):
1494 os.remove(bundle_dst)
1495 if os.path.exists(bundle_tmp):
1496 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001497 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001500 keep = True
1501 done = False
1502 dest = open(tmpPath, 'a+b')
1503 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001504 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001505 pos = dest.tell()
1506
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001507 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001508 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001509 req = urllib2.Request(srcUrl)
1510 if pos > 0:
1511 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001512
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001513 try:
1514 r = urllib2.urlopen(req)
1515 except urllib2.HTTPError, e:
1516 def _content_type():
1517 try:
1518 return e.info()['content-type']
1519 except:
1520 return None
1521
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001522 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001523 keep = False
1524 return False
1525 elif _content_type() == 'text/plain':
1526 try:
1527 msg = e.read()
1528 if len(msg) > 0 and msg[-1] == '\n':
1529 msg = msg[0:-1]
1530 msg = ' (%s)' % msg
1531 except:
1532 msg = ''
1533 else:
1534 try:
1535 from BaseHTTPServer import BaseHTTPRequestHandler
1536 res = BaseHTTPRequestHandler.responses[e.code]
1537 msg = ' (%s: %s)' % (res[0], res[1])
1538 except:
1539 msg = ''
1540 raise DownloadError('HTTP %s%s' % (e.code, msg))
1541 except urllib2.URLError, e:
1542 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1543 finally:
1544 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001545
1546 p = None
1547 try:
Conley Owens43bda842012-03-12 11:25:04 -07001548 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001549 unit = 1 << 10
1550
1551 if size and not quiet:
1552 if size > 1024 * 1.3:
1553 unit = 1 << 20
1554 desc = 'MB'
1555 else:
1556 desc = 'KB'
1557 p = Progress(
1558 'Downloading %s' % self.relpath,
1559 int(size) / unit,
1560 units=desc)
1561 if pos > 0:
1562 p.update(pos / unit)
1563
1564 s = 0
1565 while True:
1566 d = r.read(8192)
1567 if d == '':
1568 done = True
1569 return True
1570 dest.write(d)
1571 if p:
1572 s += len(d)
1573 if s >= unit:
1574 p.update(s / unit)
1575 s = s % unit
1576 if p:
1577 if s >= unit:
1578 p.update(s / unit)
1579 else:
1580 p.update(1)
1581 finally:
1582 r.close()
1583 if p:
1584 p.end()
1585 finally:
1586 dest.close()
1587
1588 if os.path.exists(dstPath):
1589 os.remove(dstPath)
1590 if done:
1591 os.rename(tmpPath, dstPath)
1592 elif not keep:
1593 os.remove(tmpPath)
1594
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001595 def _Checkout(self, rev, quiet=False):
1596 cmd = ['checkout']
1597 if quiet:
1598 cmd.append('-q')
1599 cmd.append(rev)
1600 cmd.append('--')
1601 if GitCommand(self, cmd).Wait() != 0:
1602 if self._allrefs:
1603 raise GitError('%s checkout %s ' % (self.name, rev))
1604
1605 def _ResetHard(self, rev, quiet=True):
1606 cmd = ['reset', '--hard']
1607 if quiet:
1608 cmd.append('-q')
1609 cmd.append(rev)
1610 if GitCommand(self, cmd).Wait() != 0:
1611 raise GitError('%s reset --hard %s ' % (self.name, rev))
1612
1613 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001614 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001615 if onto is not None:
1616 cmd.extend(['--onto', onto])
1617 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001618 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001619 raise GitError('%s rebase %s ' % (self.name, upstream))
1620
1621 def _FastForward(self, head):
1622 cmd = ['merge', head]
1623 if GitCommand(self, cmd).Wait() != 0:
1624 raise GitError('%s merge %s ' % (self.name, head))
1625
1626 def _InitGitDir(self):
1627 if not os.path.exists(self.gitdir):
1628 os.makedirs(self.gitdir)
1629 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001630
Shawn O. Pearce88443382010-10-08 10:02:09 +02001631 mp = self.manifest.manifestProject
1632 ref_dir = mp.config.GetString('repo.reference')
1633
1634 if ref_dir:
1635 mirror_git = os.path.join(ref_dir, self.name + '.git')
1636 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1637 self.relpath + '.git')
1638
1639 if os.path.exists(mirror_git):
1640 ref_dir = mirror_git
1641
1642 elif os.path.exists(repo_git):
1643 ref_dir = repo_git
1644
1645 else:
1646 ref_dir = None
1647
1648 if ref_dir:
1649 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1650 os.path.join(ref_dir, 'objects') + '\n')
1651
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001652 if self.manifest.IsMirror:
1653 self.config.SetString('core.bare', 'true')
1654 else:
1655 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656
1657 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001658 try:
1659 to_rm = os.listdir(hooks)
1660 except OSError:
1661 to_rm = []
1662 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001664 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665
1666 m = self.manifest.manifestProject.config
1667 for key in ['user.name', 'user.email']:
1668 if m.Has(key, include_defaults = False):
1669 self.config.SetString(key, m.GetString(key))
1670
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001671 def _InitHooks(self):
1672 hooks = self._gitdir_path('hooks')
1673 if not os.path.exists(hooks):
1674 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001675 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001676 name = os.path.basename(stock_hook)
1677
Victor Boivie65e0f352011-04-18 11:23:29 +02001678 if name in ('commit-msg',) and not self.remote.review \
1679 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001680 # Don't install a Gerrit Code Review hook if this
1681 # project does not appear to use it for reviews.
1682 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001683 # Since the manifest project is one of those, but also
1684 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001685 continue
1686
1687 dst = os.path.join(hooks, name)
1688 if os.path.islink(dst):
1689 continue
1690 if os.path.exists(dst):
1691 if filecmp.cmp(stock_hook, dst, shallow=False):
1692 os.remove(dst)
1693 else:
1694 _error("%s: Not replacing %s hook", self.relpath, name)
1695 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001696 try:
1697 os.symlink(relpath(stock_hook, dst), dst)
1698 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001699 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001700 raise GitError('filesystem must support symlinks')
1701 else:
1702 raise
1703
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001705 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001707 remote.url = self.remote.url
1708 remote.review = self.remote.review
1709 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001711 if self.worktree:
1712 remote.ResetFetch(mirror=False)
1713 else:
1714 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715 remote.Save()
1716
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717 def _InitMRef(self):
1718 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001719 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001721 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001722 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001723
1724 def _InitAnyMRef(self, ref):
1725 cur = self.bare_ref.symref(ref)
1726
1727 if self.revisionId:
1728 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1729 msg = 'manifest set to %s' % self.revisionId
1730 dst = self.revisionId + '^0'
1731 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1732 else:
1733 remote = self.GetRemote(self.remote.name)
1734 dst = remote.ToLocal(self.revisionExpr)
1735 if cur != dst:
1736 msg = 'manifest set to %s' % self.revisionExpr
1737 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001738
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 def _InitWorkTree(self):
1740 dotgit = os.path.join(self.worktree, '.git')
1741 if not os.path.exists(dotgit):
1742 os.makedirs(dotgit)
1743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001744 for name in ['config',
1745 'description',
1746 'hooks',
1747 'info',
1748 'logs',
1749 'objects',
1750 'packed-refs',
1751 'refs',
1752 'rr-cache',
1753 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001754 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001755 src = os.path.join(self.gitdir, name)
1756 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001757 if os.path.islink(dst) or not os.path.exists(dst):
1758 os.symlink(relpath(src, dst), dst)
1759 else:
1760 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001761 except OSError, e:
1762 if e.errno == errno.EPERM:
1763 raise GitError('filesystem must support symlinks')
1764 else:
1765 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001767 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
1769 cmd = ['read-tree', '--reset', '-u']
1770 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001771 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772 if GitCommand(self, cmd).Wait() != 0:
1773 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001774
1775 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1776 if not os.path.exists(rr_cache):
1777 os.makedirs(rr_cache)
1778
Shawn O. Pearce93609662009-04-21 10:50:33 -07001779 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780
1781 def _gitdir_path(self, path):
1782 return os.path.join(self.gitdir, path)
1783
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001784 def _revlist(self, *args, **kw):
1785 a = []
1786 a.extend(args)
1787 a.append('--')
1788 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789
1790 @property
1791 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001792 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001793
1794 class _GitGetByExec(object):
1795 def __init__(self, project, bare):
1796 self._project = project
1797 self._bare = bare
1798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001799 def LsOthers(self):
1800 p = GitCommand(self._project,
1801 ['ls-files',
1802 '-z',
1803 '--others',
1804 '--exclude-standard'],
1805 bare = False,
1806 capture_stdout = True,
1807 capture_stderr = True)
1808 if p.Wait() == 0:
1809 out = p.stdout
1810 if out:
1811 return out[:-1].split("\0")
1812 return []
1813
1814 def DiffZ(self, name, *args):
1815 cmd = [name]
1816 cmd.append('-z')
1817 cmd.extend(args)
1818 p = GitCommand(self._project,
1819 cmd,
1820 bare = False,
1821 capture_stdout = True,
1822 capture_stderr = True)
1823 try:
1824 out = p.process.stdout.read()
1825 r = {}
1826 if out:
1827 out = iter(out[:-1].split('\0'))
1828 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001829 try:
1830 info = out.next()
1831 path = out.next()
1832 except StopIteration:
1833 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834
1835 class _Info(object):
1836 def __init__(self, path, omode, nmode, oid, nid, state):
1837 self.path = path
1838 self.src_path = None
1839 self.old_mode = omode
1840 self.new_mode = nmode
1841 self.old_id = oid
1842 self.new_id = nid
1843
1844 if len(state) == 1:
1845 self.status = state
1846 self.level = None
1847 else:
1848 self.status = state[:1]
1849 self.level = state[1:]
1850 while self.level.startswith('0'):
1851 self.level = self.level[1:]
1852
1853 info = info[1:].split(' ')
1854 info =_Info(path, *info)
1855 if info.status in ('R', 'C'):
1856 info.src_path = info.path
1857 info.path = out.next()
1858 r[info.path] = info
1859 return r
1860 finally:
1861 p.Wait()
1862
1863 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001864 if self._bare:
1865 path = os.path.join(self._project.gitdir, HEAD)
1866 else:
1867 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001868 fd = open(path, 'rb')
1869 try:
1870 line = fd.read()
1871 finally:
1872 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001873 if line.startswith('ref: '):
1874 return line[5:-1]
1875 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001876
1877 def SetHead(self, ref, message=None):
1878 cmdv = []
1879 if message is not None:
1880 cmdv.extend(['-m', message])
1881 cmdv.append(HEAD)
1882 cmdv.append(ref)
1883 self.symbolic_ref(*cmdv)
1884
1885 def DetachHead(self, new, message=None):
1886 cmdv = ['--no-deref']
1887 if message is not None:
1888 cmdv.extend(['-m', message])
1889 cmdv.append(HEAD)
1890 cmdv.append(new)
1891 self.update_ref(*cmdv)
1892
1893 def UpdateRef(self, name, new, old=None,
1894 message=None,
1895 detach=False):
1896 cmdv = []
1897 if message is not None:
1898 cmdv.extend(['-m', message])
1899 if detach:
1900 cmdv.append('--no-deref')
1901 cmdv.append(name)
1902 cmdv.append(new)
1903 if old is not None:
1904 cmdv.append(old)
1905 self.update_ref(*cmdv)
1906
1907 def DeleteRef(self, name, old=None):
1908 if not old:
1909 old = self.rev_parse(name)
1910 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001911 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001912
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001913 def rev_list(self, *args, **kw):
1914 if 'format' in kw:
1915 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1916 else:
1917 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001918 cmdv.extend(args)
1919 p = GitCommand(self._project,
1920 cmdv,
1921 bare = self._bare,
1922 capture_stdout = True,
1923 capture_stderr = True)
1924 r = []
1925 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001926 if line[-1] == '\n':
1927 line = line[:-1]
1928 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001929 if p.Wait() != 0:
1930 raise GitError('%s rev-list %s: %s' % (
1931 self._project.name,
1932 str(args),
1933 p.stderr))
1934 return r
1935
1936 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001937 """Allow arbitrary git commands using pythonic syntax.
1938
1939 This allows you to do things like:
1940 git_obj.rev_parse('HEAD')
1941
1942 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1943 run. We'll replace the '_' with a '-' and try to run a git command.
1944 Any other arguments will be passed to the git command.
1945
1946 Args:
1947 name: The name of the git command to call. Any '_' characters will
1948 be replaced with '-'.
1949
1950 Returns:
1951 A callable object that will try to call git with the named command.
1952 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001953 name = name.replace('_', '-')
1954 def runner(*args):
1955 cmdv = [name]
1956 cmdv.extend(args)
1957 p = GitCommand(self._project,
1958 cmdv,
1959 bare = self._bare,
1960 capture_stdout = True,
1961 capture_stderr = True)
1962 if p.Wait() != 0:
1963 raise GitError('%s %s: %s' % (
1964 self._project.name,
1965 name,
1966 p.stderr))
1967 r = p.stdout
1968 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1969 return r[:-1]
1970 return r
1971 return runner
1972
1973
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001974class _PriorSyncFailedError(Exception):
1975 def __str__(self):
1976 return 'prior sync failed; rebase still in progress'
1977
1978class _DirtyError(Exception):
1979 def __str__(self):
1980 return 'contains uncommitted changes'
1981
1982class _InfoMessage(object):
1983 def __init__(self, project, text):
1984 self.project = project
1985 self.text = text
1986
1987 def Print(self, syncbuf):
1988 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1989 syncbuf.out.nl()
1990
1991class _Failure(object):
1992 def __init__(self, project, why):
1993 self.project = project
1994 self.why = why
1995
1996 def Print(self, syncbuf):
1997 syncbuf.out.fail('error: %s/: %s',
1998 self.project.relpath,
1999 str(self.why))
2000 syncbuf.out.nl()
2001
2002class _Later(object):
2003 def __init__(self, project, action):
2004 self.project = project
2005 self.action = action
2006
2007 def Run(self, syncbuf):
2008 out = syncbuf.out
2009 out.project('project %s/', self.project.relpath)
2010 out.nl()
2011 try:
2012 self.action()
2013 out.nl()
2014 return True
2015 except GitError, e:
2016 out.nl()
2017 return False
2018
2019class _SyncColoring(Coloring):
2020 def __init__(self, config):
2021 Coloring.__init__(self, config, 'reposync')
2022 self.project = self.printer('header', attr = 'bold')
2023 self.info = self.printer('info')
2024 self.fail = self.printer('fail', fg='red')
2025
2026class SyncBuffer(object):
2027 def __init__(self, config, detach_head=False):
2028 self._messages = []
2029 self._failures = []
2030 self._later_queue1 = []
2031 self._later_queue2 = []
2032
2033 self.out = _SyncColoring(config)
2034 self.out.redirect(sys.stderr)
2035
2036 self.detach_head = detach_head
2037 self.clean = True
2038
2039 def info(self, project, fmt, *args):
2040 self._messages.append(_InfoMessage(project, fmt % args))
2041
2042 def fail(self, project, err=None):
2043 self._failures.append(_Failure(project, err))
2044 self.clean = False
2045
2046 def later1(self, project, what):
2047 self._later_queue1.append(_Later(project, what))
2048
2049 def later2(self, project, what):
2050 self._later_queue2.append(_Later(project, what))
2051
2052 def Finish(self):
2053 self._PrintMessages()
2054 self._RunLater()
2055 self._PrintMessages()
2056 return self.clean
2057
2058 def _RunLater(self):
2059 for q in ['_later_queue1', '_later_queue2']:
2060 if not self._RunQueue(q):
2061 return
2062
2063 def _RunQueue(self, queue):
2064 for m in getattr(self, queue):
2065 if not m.Run(self):
2066 self.clean = False
2067 return False
2068 setattr(self, queue, [])
2069 return True
2070
2071 def _PrintMessages(self):
2072 for m in self._messages:
2073 m.Print(self)
2074 for m in self._failures:
2075 m.Print(self)
2076
2077 self._messages = []
2078 self._failures = []
2079
2080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081class MetaProject(Project):
2082 """A special project housed under .repo.
2083 """
2084 def __init__(self, manifest, name, gitdir, worktree):
2085 repodir = manifest.repodir
2086 Project.__init__(self,
2087 manifest = manifest,
2088 name = name,
2089 gitdir = gitdir,
2090 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002091 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002092 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002093 revisionExpr = 'refs/heads/master',
2094 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002095
2096 def PreSync(self):
2097 if self.Exists:
2098 cb = self.CurrentBranch
2099 if cb:
2100 base = self.GetBranch(cb).merge
2101 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002102 self.revisionExpr = base
2103 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104
2105 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002106 def LastFetch(self):
2107 try:
2108 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2109 return os.path.getmtime(fh)
2110 except OSError:
2111 return 0
2112
2113 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002114 def HasChanges(self):
2115 """Has the remote received new commits not yet checked out?
2116 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002117 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002118 return False
2119
2120 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002121 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002122 head = self.work_git.GetHead()
2123 if head.startswith(R_HEADS):
2124 try:
2125 head = all[head]
2126 except KeyError:
2127 head = None
2128
2129 if revid == head:
2130 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002131 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 return True
2133 return False