blob: b2eaa87863e1ab08a49bcd3b6a6b2aedbc537dce [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,
Colin Cross5acde752012-03-28 20:15:45 -0700507 rebase = True,
508 groups = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509 self.manifest = manifest
510 self.name = name
511 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800512 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800513 if worktree:
514 self.worktree = worktree.replace('\\', '/')
515 else:
516 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 self.revisionExpr = revisionExpr
519
520 if revisionId is None \
521 and revisionExpr \
522 and IsId(revisionExpr):
523 self.revisionId = revisionExpr
524 else:
525 self.revisionId = revisionId
526
Mike Pontillod3153822012-02-28 11:53:24 -0800527 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700528 self.groups = groups
Mike Pontillod3153822012-02-28 11:53:24 -0800529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531 self.copyfiles = []
532 self.config = GitConfig.ForRepository(
533 gitdir = self.gitdir,
534 defaults = self.manifest.globalConfig)
535
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800536 if self.worktree:
537 self.work_git = self._GitGetByExec(self, bare=False)
538 else:
539 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700541 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542
Doug Anderson37282b42011-03-04 11:54:18 -0800543 # This will be filled in if a project is later identified to be the
544 # project containing repo hooks.
545 self.enabled_repo_hooks = []
546
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700547 @property
548 def Exists(self):
549 return os.path.isdir(self.gitdir)
550
551 @property
552 def CurrentBranch(self):
553 """Obtain the name of the currently checked out branch.
554 The branch name omits the 'refs/heads/' prefix.
555 None is returned if the project is on a detached HEAD.
556 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700557 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558 if b.startswith(R_HEADS):
559 return b[len(R_HEADS):]
560 return None
561
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700562 def IsRebaseInProgress(self):
563 w = self.worktree
564 g = os.path.join(w, '.git')
565 return os.path.exists(os.path.join(g, 'rebase-apply')) \
566 or os.path.exists(os.path.join(g, 'rebase-merge')) \
567 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200568
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700569 def IsDirty(self, consider_untracked=True):
570 """Is the working directory modified in some way?
571 """
572 self.work_git.update_index('-q',
573 '--unmerged',
574 '--ignore-missing',
575 '--refresh')
576 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
577 return True
578 if self.work_git.DiffZ('diff-files'):
579 return True
580 if consider_untracked and self.work_git.LsOthers():
581 return True
582 return False
583
584 _userident_name = None
585 _userident_email = None
586
587 @property
588 def UserName(self):
589 """Obtain the user's personal name.
590 """
591 if self._userident_name is None:
592 self._LoadUserIdentity()
593 return self._userident_name
594
595 @property
596 def UserEmail(self):
597 """Obtain the user's email address. This is very likely
598 to be their Gerrit login.
599 """
600 if self._userident_email is None:
601 self._LoadUserIdentity()
602 return self._userident_email
603
604 def _LoadUserIdentity(self):
605 u = self.bare_git.var('GIT_COMMITTER_IDENT')
606 m = re.compile("^(.*) <([^>]*)> ").match(u)
607 if m:
608 self._userident_name = m.group(1)
609 self._userident_email = m.group(2)
610 else:
611 self._userident_name = ''
612 self._userident_email = ''
613
614 def GetRemote(self, name):
615 """Get the configuration for a single remote.
616 """
617 return self.config.GetRemote(name)
618
619 def GetBranch(self, name):
620 """Get the configuration for a single branch.
621 """
622 return self.config.GetBranch(name)
623
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700624 def GetBranches(self):
625 """Get all existing local branches.
626 """
627 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700628 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700629 heads = {}
630 pubd = {}
631
632 for name, id in all.iteritems():
633 if name.startswith(R_HEADS):
634 name = name[len(R_HEADS):]
635 b = self.GetBranch(name)
636 b.current = name == current
637 b.published = None
638 b.revision = id
639 heads[name] = b
640
641 for name, id in all.iteritems():
642 if name.startswith(R_PUB):
643 name = name[len(R_PUB):]
644 b = heads.get(name)
645 if b:
646 b.published = id
647
648 return heads
649
Colin Cross5acde752012-03-28 20:15:45 -0700650 def MatchesGroups(self, manifest_groups):
651 """Returns true if the manifest groups specified at init should cause
652 this project to be synced.
653 Prefixing a manifest group with "-" inverts the meaning of a group.
654 All projects are implicitly labelled with "default" unless they are
655 explicitly labelled "-default".
656 If any non-inverted manifest groups are specified, the default label
657 is ignored.
658 Specifying only inverted groups implies "default".
659 """
660 project_groups = self.groups
661 if not manifest_groups:
662 return not project_groups or not "-default" in project_groups
663
664 if not project_groups:
665 project_groups = ["default"]
666 elif not ("default" in project_groups or "-default" in project_groups):
667 project_groups.append("default")
668
669 plus_groups = [x for x in manifest_groups if not x.startswith("-")]
670 minus_groups = [x[1:] for x in manifest_groups if x.startswith("-")]
671
672 if not plus_groups:
673 plus_groups.append("default")
674
675 for group in minus_groups:
676 if group in project_groups:
677 # project was excluded by -group
678 return False
679
680 for group in plus_groups:
681 if group in project_groups:
682 # project was included by group
683 return True
684
685 # groups were specified that did not include this project
686 if plus_groups:
687 return False
688 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689
690## Status Display ##
691
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500692 def HasChanges(self):
693 """Returns true if there are uncommitted changes.
694 """
695 self.work_git.update_index('-q',
696 '--unmerged',
697 '--ignore-missing',
698 '--refresh')
699 if self.IsRebaseInProgress():
700 return True
701
702 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
703 return True
704
705 if self.work_git.DiffZ('diff-files'):
706 return True
707
708 if self.work_git.LsOthers():
709 return True
710
711 return False
712
Terence Haddock4655e812011-03-31 12:33:34 +0200713 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200715
716 Args:
717 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 """
719 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200720 if output_redir == None:
721 output_redir = sys.stdout
722 print >>output_redir, ''
723 print >>output_redir, 'project %s/' % self.relpath
724 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 return
726
727 self.work_git.update_index('-q',
728 '--unmerged',
729 '--ignore-missing',
730 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700731 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
733 df = self.work_git.DiffZ('diff-files')
734 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100735 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700736 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200739 if not output_redir == None:
740 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 out.project('project %-40s', self.relpath + '/')
742
743 branch = self.CurrentBranch
744 if branch is None:
745 out.nobranch('(*** NO BRANCH ***)')
746 else:
747 out.branch('branch %s', branch)
748 out.nl()
749
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700750 if rb:
751 out.important('prior sync failed; rebase still in progress')
752 out.nl()
753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754 paths = list()
755 paths.extend(di.keys())
756 paths.extend(df.keys())
757 paths.extend(do)
758
759 paths = list(set(paths))
760 paths.sort()
761
762 for p in paths:
763 try: i = di[p]
764 except KeyError: i = None
765
766 try: f = df[p]
767 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 if i: i_status = i.status.upper()
770 else: i_status = '-'
771
772 if f: f_status = f.status.lower()
773 else: f_status = '-'
774
775 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800776 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 i.src_path, p, i.level)
778 else:
779 line = ' %s%s\t%s' % (i_status, f_status, p)
780
781 if i and not f:
782 out.added('%s', line)
783 elif (i and f) or (not i and f):
784 out.changed('%s', line)
785 elif not i and not f:
786 out.untracked('%s', line)
787 else:
788 out.write('%s', line)
789 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200790
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700791 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792
pelyad67872d2012-03-28 14:49:58 +0300793 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794 """Prints the status of the repository to stdout.
795 """
796 out = DiffColoring(self.config)
797 cmd = ['diff']
798 if out.is_on:
799 cmd.append('--color')
800 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300801 if absolute_paths:
802 cmd.append('--src-prefix=a/%s/' % self.relpath)
803 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 cmd.append('--')
805 p = GitCommand(self,
806 cmd,
807 capture_stdout = True,
808 capture_stderr = True)
809 has_diff = False
810 for line in p.process.stdout:
811 if not has_diff:
812 out.nl()
813 out.project('project %s/' % self.relpath)
814 out.nl()
815 has_diff = True
816 print line[:-1]
817 p.Wait()
818
819
820## Publish / Upload ##
821
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700822 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 """Was the branch published (uploaded) for code review?
824 If so, returns the SHA-1 hash of the last published
825 state for the branch.
826 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700827 key = R_PUB + branch
828 if all is None:
829 try:
830 return self.bare_git.rev_parse(key)
831 except GitError:
832 return None
833 else:
834 try:
835 return all[key]
836 except KeyError:
837 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700839 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840 """Prunes any stale published refs.
841 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700842 if all is None:
843 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 heads = set()
845 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700846 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 if name.startswith(R_HEADS):
848 heads.add(name)
849 elif name.startswith(R_PUB):
850 canrm[name] = id
851
852 for name, id in canrm.iteritems():
853 n = name[len(R_PUB):]
854 if R_HEADS + n not in heads:
855 self.bare_git.DeleteRef(name, id)
856
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700857 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 """List any branches which can be uploaded for review.
859 """
860 heads = {}
861 pubed = {}
862
863 for name, id in self._allrefs.iteritems():
864 if name.startswith(R_HEADS):
865 heads[name[len(R_HEADS):]] = id
866 elif name.startswith(R_PUB):
867 pubed[name[len(R_PUB):]] = id
868
869 ready = []
870 for branch, id in heads.iteritems():
871 if branch in pubed and pubed[branch] == id:
872 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700873 if selected_branch and branch != selected_branch:
874 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800876 rb = self.GetUploadableBranch(branch)
877 if rb:
878 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 return ready
880
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800881 def GetUploadableBranch(self, branch_name):
882 """Get a single uploadable branch, or None.
883 """
884 branch = self.GetBranch(branch_name)
885 base = branch.LocalMerge
886 if branch.LocalMerge:
887 rb = ReviewableBranch(self, branch, base)
888 if rb.commits:
889 return rb
890 return None
891
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700892 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700893 people=([],[]),
894 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 """Uploads the named branch for code review.
896 """
897 if branch is None:
898 branch = self.CurrentBranch
899 if branch is None:
900 raise GitError('not currently on a branch')
901
902 branch = self.GetBranch(branch)
903 if not branch.LocalMerge:
904 raise GitError('branch %s does not track a remote' % branch.name)
905 if not branch.remote.review:
906 raise GitError('remote %s has no review url' % branch.remote.name)
907
908 dest_branch = branch.merge
909 if not dest_branch.startswith(R_HEADS):
910 dest_branch = R_HEADS + dest_branch
911
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800912 if not branch.remote.projectname:
913 branch.remote.projectname = self.name
914 branch.remote.Save()
915
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800916 url = branch.remote.ReviewUrl(self.UserEmail)
917 if url is None:
918 raise UploadError('review not configured')
919 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800920
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800921 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800922 rp = ['gerrit receive-pack']
923 for e in people[0]:
924 rp.append('--reviewer=%s' % sq(e))
925 for e in people[1]:
926 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800927 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700928
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800929 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800930
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800931 if dest_branch.startswith(R_HEADS):
932 dest_branch = dest_branch[len(R_HEADS):]
933 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
934 if auto_topic:
935 ref_spec = ref_spec + '/' + branch.name
936 cmd.append(ref_spec)
937
938 if GitCommand(self, cmd, bare = True).Wait() != 0:
939 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940
941 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
942 self.bare_git.UpdateRef(R_PUB + branch.name,
943 R_HEADS + branch.name,
944 message = msg)
945
946
947## Sync ##
948
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700949 def Sync_NetworkHalf(self,
950 quiet=False,
951 is_new=None,
952 current_branch_only=False,
953 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 """Perform only the network IO portion of the sync process.
955 Local working directory/branch state is not affected.
956 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700957 if is_new is None:
958 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200959 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 self._InitGitDir()
961 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700962
963 if is_new:
964 alt = os.path.join(self.gitdir, 'objects/info/alternates')
965 try:
966 fd = open(alt, 'rb')
967 try:
968 alt_dir = fd.readline().rstrip()
969 finally:
970 fd.close()
971 except IOError:
972 alt_dir = None
973 else:
974 alt_dir = None
975
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700976 if clone_bundle \
977 and alt_dir is None \
978 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700979 is_new = False
980
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700981 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
982 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800984
985 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800986 self._InitMRef()
987 else:
988 self._InitMirrorHead()
989 try:
990 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
991 except OSError:
992 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800994
995 def PostRepoUpgrade(self):
996 self._InitHooks()
997
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 def _CopyFiles(self):
999 for file in self.copyfiles:
1000 file._Copy()
1001
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001002 def GetRevisionId(self, all=None):
1003 if self.revisionId:
1004 return self.revisionId
1005
1006 rem = self.GetRemote(self.remote.name)
1007 rev = rem.ToLocal(self.revisionExpr)
1008
1009 if all is not None and rev in all:
1010 return all[rev]
1011
1012 try:
1013 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1014 except GitError:
1015 raise ManifestInvalidRevisionError(
1016 'revision %s in %s not found' % (self.revisionExpr,
1017 self.name))
1018
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001019 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 """Perform only the local IO portion of the sync process.
1021 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001023 all = self.bare_ref.all
1024 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001025 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001026
1027 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001028 head = self.work_git.GetHead()
1029 if head.startswith(R_HEADS):
1030 branch = head[len(R_HEADS):]
1031 try:
1032 head = all[head]
1033 except KeyError:
1034 head = None
1035 else:
1036 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001038 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 # Currently on a detached HEAD. The user is assumed to
1040 # not have any local modifications worth worrying about.
1041 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001042 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001043 syncbuf.fail(self, _PriorSyncFailedError())
1044 return
1045
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001046 if head == revid:
1047 # No changes; don't do anything further.
1048 #
1049 return
1050
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001051 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001055 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 except GitError, e:
1057 syncbuf.fail(self, e)
1058 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001060 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001062 if head == revid:
1063 # No changes; don't do anything further.
1064 #
1065 return
1066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001069 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001071 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001073 syncbuf.info(self,
1074 "leaving %s; does not track upstream",
1075 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001077 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001078 except GitError, e:
1079 syncbuf.fail(self, e)
1080 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001084 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001085 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001087 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 if not_merged:
1089 if upstream_gain:
1090 # The user has published this branch and some of those
1091 # commits are not yet merged upstream. We do not want
1092 # to rewrite the published commits so we punt.
1093 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001094 syncbuf.fail(self,
1095 "branch %s is published (but not merged) and is now %d commits behind"
1096 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001097 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001098 elif pub == head:
1099 # All published commits are merged, and thus we are a
1100 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001101 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001103 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 self._CopyFiles()
1105 syncbuf.later1(self, _doff)
1106 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001108 # Examine the local commits not in the remote. Find the
1109 # last one attributed to this user, if any.
1110 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001111 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001112 last_mine = None
1113 cnt_mine = 0
1114 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001115 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001116 if committer_email == self.UserEmail:
1117 last_mine = commit_id
1118 cnt_mine += 1
1119
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001120 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122
1123 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001124 syncbuf.fail(self, _DirtyError())
1125 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001127 # If the upstream switched on us, warn the user.
1128 #
1129 if branch.merge != self.revisionExpr:
1130 if branch.merge and self.revisionExpr:
1131 syncbuf.info(self,
1132 'manifest switched %s...%s',
1133 branch.merge,
1134 self.revisionExpr)
1135 elif branch.merge:
1136 syncbuf.info(self,
1137 'manifest no longer tracks %s',
1138 branch.merge)
1139
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001140 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001142 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 syncbuf.info(self,
1145 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001146 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001148 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001149 if not ID_RE.match(self.revisionExpr):
1150 # in case of manifest sync the revisionExpr might be a SHA1
1151 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 branch.Save()
1153
Mike Pontillod3153822012-02-28 11:53:24 -08001154 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001155 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001156 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001157 self._CopyFiles()
1158 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001159 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001161 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001162 self._CopyFiles()
1163 except GitError, e:
1164 syncbuf.fail(self, e)
1165 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001167 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001168 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001169 self._CopyFiles()
1170 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001172 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173 # dest should already be an absolute path, but src is project relative
1174 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001175 abssrc = os.path.join(self.worktree, src)
1176 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001177
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001178 def DownloadPatchSet(self, change_id, patch_id):
1179 """Download a single patch set of a single change to FETCH_HEAD.
1180 """
1181 remote = self.GetRemote(self.remote.name)
1182
1183 cmd = ['fetch', remote.name]
1184 cmd.append('refs/changes/%2.2d/%d/%d' \
1185 % (change_id % 100, change_id, patch_id))
1186 cmd.extend(map(lambda x: str(x), remote.fetch))
1187 if GitCommand(self, cmd, bare=True).Wait() != 0:
1188 return None
1189 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001191 change_id,
1192 patch_id,
1193 self.bare_git.rev_parse('FETCH_HEAD'))
1194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195
1196## Branch Management ##
1197
1198 def StartBranch(self, name):
1199 """Create a new branch off the manifest's revision.
1200 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001201 head = self.work_git.GetHead()
1202 if head == (R_HEADS + name):
1203 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001205 all = self.bare_ref.all
1206 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001207 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001208 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001209 capture_stdout = True,
1210 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001211
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001212 branch = self.GetBranch(name)
1213 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 branch.merge = self.revisionExpr
1215 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001216
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001217 if head.startswith(R_HEADS):
1218 try:
1219 head = all[head]
1220 except KeyError:
1221 head = None
1222
1223 if revid and head and revid == head:
1224 ref = os.path.join(self.gitdir, R_HEADS + name)
1225 try:
1226 os.makedirs(os.path.dirname(ref))
1227 except OSError:
1228 pass
1229 _lwrite(ref, '%s\n' % revid)
1230 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1231 'ref: %s%s\n' % (R_HEADS, name))
1232 branch.Save()
1233 return True
1234
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001235 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001237 capture_stdout = True,
1238 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001239 branch.Save()
1240 return True
1241 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Wink Saville02d79452009-04-10 13:01:24 -07001243 def CheckoutBranch(self, name):
1244 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001245
1246 Args:
1247 name: The name of the branch to checkout.
1248
1249 Returns:
1250 True if the checkout succeeded; False if it didn't; None if the branch
1251 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001252 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001253 rev = R_HEADS + name
1254 head = self.work_git.GetHead()
1255 if head == rev:
1256 # Already on the branch
1257 #
1258 return True
Wink Saville02d79452009-04-10 13:01:24 -07001259
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001261 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 revid = all[rev]
1263 except KeyError:
1264 # Branch does not exist in this project
1265 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001266 return None
Wink Saville02d79452009-04-10 13:01:24 -07001267
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001268 if head.startswith(R_HEADS):
1269 try:
1270 head = all[head]
1271 except KeyError:
1272 head = None
1273
1274 if head == revid:
1275 # Same revision; just update HEAD to point to the new
1276 # target branch, but otherwise take no other action.
1277 #
1278 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1279 'ref: %s%s\n' % (R_HEADS, name))
1280 return True
1281
1282 return GitCommand(self,
1283 ['checkout', name, '--'],
1284 capture_stdout = True,
1285 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001286
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001287 def AbandonBranch(self, name):
1288 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001289
1290 Args:
1291 name: The name of the branch to abandon.
1292
1293 Returns:
1294 True if the abandon succeeded; False if it didn't; None if the branch
1295 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001296 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001297 rev = R_HEADS + name
1298 all = self.bare_ref.all
1299 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001300 # Doesn't exist
1301 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001302
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001303 head = self.work_git.GetHead()
1304 if head == rev:
1305 # We can't destroy the branch while we are sitting
1306 # on it. Switch to a detached HEAD.
1307 #
1308 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001309
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 revid = self.GetRevisionId(all)
1311 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001312 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1313 '%s\n' % revid)
1314 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001315 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001316
1317 return GitCommand(self,
1318 ['branch', '-D', name],
1319 capture_stdout = True,
1320 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 def PruneHeads(self):
1323 """Prune any topic branches already merged into upstream.
1324 """
1325 cb = self.CurrentBranch
1326 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001327 left = self._allrefs
1328 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 if name.startswith(R_HEADS):
1330 name = name[len(R_HEADS):]
1331 if cb is None or name != cb:
1332 kill.append(name)
1333
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001334 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 if cb is not None \
1336 and not self._revlist(HEAD + '...' + rev) \
1337 and not self.IsDirty(consider_untracked = False):
1338 self.work_git.DetachHead(HEAD)
1339 kill.append(cb)
1340
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001342 old = self.bare_git.GetHead()
1343 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1345
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 try:
1347 self.bare_git.DetachHead(rev)
1348
1349 b = ['branch', '-d']
1350 b.extend(kill)
1351 b = GitCommand(self, b, bare=True,
1352 capture_stdout=True,
1353 capture_stderr=True)
1354 b.Wait()
1355 finally:
1356 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001357 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001359 for branch in kill:
1360 if (R_HEADS + branch) not in left:
1361 self.CleanPublishedCache()
1362 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
1364 if cb and cb not in kill:
1365 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001366 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367
1368 kept = []
1369 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001370 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 branch = self.GetBranch(branch)
1372 base = branch.LocalMerge
1373 if not base:
1374 base = rev
1375 kept.append(ReviewableBranch(self, branch, base))
1376 return kept
1377
1378
1379## Direct Git Commands ##
1380
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001381 def _RemoteFetch(self, name=None,
1382 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001383 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001384 quiet=False,
1385 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001386
1387 is_sha1 = False
1388 tag_name = None
1389
1390 if current_branch_only:
1391 if ID_RE.match(self.revisionExpr) is not None:
1392 is_sha1 = True
1393 elif self.revisionExpr.startswith(R_TAGS):
1394 # this is a tag and its sha1 value should never change
1395 tag_name = self.revisionExpr[len(R_TAGS):]
1396
1397 if is_sha1 or tag_name is not None:
1398 try:
1399 self.GetRevisionId()
1400 return True
1401 except ManifestInvalidRevisionError:
1402 # There is no such persistent revision. We have to fetch it.
1403 pass
1404
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405 if not name:
1406 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001407
1408 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001409 remote = self.GetRemote(name)
1410 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001411 ssh_proxy = True
1412
Shawn O. Pearce88443382010-10-08 10:02:09 +02001413 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001414 if alt_dir and 'objects' == os.path.basename(alt_dir):
1415 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001416 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1417 remote = self.GetRemote(name)
1418
1419 all = self.bare_ref.all
1420 ids = set(all.values())
1421 tmp = set()
1422
1423 for r, id in GitRefs(ref_dir).all.iteritems():
1424 if r not in all:
1425 if r.startswith(R_TAGS) or remote.WritesTo(r):
1426 all[r] = id
1427 ids.add(id)
1428 continue
1429
1430 if id in ids:
1431 continue
1432
1433 r = 'refs/_alt/%s' % id
1434 all[r] = id
1435 ids.add(id)
1436 tmp.add(r)
1437
1438 ref_names = list(all.keys())
1439 ref_names.sort()
1440
1441 tmp_packed = ''
1442 old_packed = ''
1443
1444 for r in ref_names:
1445 line = '%s %s\n' % (all[r], r)
1446 tmp_packed += line
1447 if r not in tmp:
1448 old_packed += line
1449
1450 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001451 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001452 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001453
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001454 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001455
1456 # The --depth option only affects the initial fetch; after that we'll do
1457 # full fetches of changes.
1458 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1459 if depth and initial:
1460 cmd.append('--depth=%s' % depth)
1461
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001462 if quiet:
1463 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001464 if not self.worktree:
1465 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001466 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001467
1468 if not current_branch_only or is_sha1:
1469 # Fetch whole repo
1470 cmd.append('--tags')
1471 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1472 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001473 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001474 cmd.append(tag_name)
1475 else:
1476 branch = self.revisionExpr
1477 if branch.startswith(R_HEADS):
1478 branch = branch[len(R_HEADS):]
1479 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001480
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001481 ok = False
1482 for i in range(2):
1483 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1484 ok = True
1485 break
1486 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001487
1488 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001489 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001490 if old_packed != '':
1491 _lwrite(packed_refs, old_packed)
1492 else:
1493 os.remove(packed_refs)
1494 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001495 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001496
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001497 def _ApplyCloneBundle(self, initial=False, quiet=False):
1498 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1499 return False
1500
1501 remote = self.GetRemote(self.remote.name)
1502 bundle_url = remote.url + '/clone.bundle'
1503 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001504 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1505 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001506 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1507 return False
1508
1509 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1510 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1511
1512 exist_dst = os.path.exists(bundle_dst)
1513 exist_tmp = os.path.exists(bundle_tmp)
1514
1515 if not initial and not exist_dst and not exist_tmp:
1516 return False
1517
1518 if not exist_dst:
1519 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1520 if not exist_dst:
1521 return False
1522
1523 cmd = ['fetch']
1524 if quiet:
1525 cmd.append('--quiet')
1526 if not self.worktree:
1527 cmd.append('--update-head-ok')
1528 cmd.append(bundle_dst)
1529 for f in remote.fetch:
1530 cmd.append(str(f))
1531 cmd.append('refs/tags/*:refs/tags/*')
1532
1533 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001534 if os.path.exists(bundle_dst):
1535 os.remove(bundle_dst)
1536 if os.path.exists(bundle_tmp):
1537 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001538 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001540 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001541 keep = True
1542 done = False
1543 dest = open(tmpPath, 'a+b')
1544 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001545 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001546 pos = dest.tell()
1547
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001548 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001549 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001550 req = urllib2.Request(srcUrl)
1551 if pos > 0:
1552 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001553
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001554 try:
1555 r = urllib2.urlopen(req)
1556 except urllib2.HTTPError, e:
1557 def _content_type():
1558 try:
1559 return e.info()['content-type']
1560 except:
1561 return None
1562
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001563 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001564 keep = False
1565 return False
1566 elif _content_type() == 'text/plain':
1567 try:
1568 msg = e.read()
1569 if len(msg) > 0 and msg[-1] == '\n':
1570 msg = msg[0:-1]
1571 msg = ' (%s)' % msg
1572 except:
1573 msg = ''
1574 else:
1575 try:
1576 from BaseHTTPServer import BaseHTTPRequestHandler
1577 res = BaseHTTPRequestHandler.responses[e.code]
1578 msg = ' (%s: %s)' % (res[0], res[1])
1579 except:
1580 msg = ''
1581 raise DownloadError('HTTP %s%s' % (e.code, msg))
1582 except urllib2.URLError, e:
1583 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1584 finally:
1585 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001586
1587 p = None
1588 try:
Conley Owens43bda842012-03-12 11:25:04 -07001589 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001590 unit = 1 << 10
1591
1592 if size and not quiet:
1593 if size > 1024 * 1.3:
1594 unit = 1 << 20
1595 desc = 'MB'
1596 else:
1597 desc = 'KB'
1598 p = Progress(
1599 'Downloading %s' % self.relpath,
1600 int(size) / unit,
1601 units=desc)
1602 if pos > 0:
1603 p.update(pos / unit)
1604
1605 s = 0
1606 while True:
1607 d = r.read(8192)
1608 if d == '':
1609 done = True
1610 return True
1611 dest.write(d)
1612 if p:
1613 s += len(d)
1614 if s >= unit:
1615 p.update(s / unit)
1616 s = s % unit
1617 if p:
1618 if s >= unit:
1619 p.update(s / unit)
1620 else:
1621 p.update(1)
1622 finally:
1623 r.close()
1624 if p:
1625 p.end()
1626 finally:
1627 dest.close()
1628
1629 if os.path.exists(dstPath):
1630 os.remove(dstPath)
1631 if done:
1632 os.rename(tmpPath, dstPath)
1633 elif not keep:
1634 os.remove(tmpPath)
1635
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001636 def _Checkout(self, rev, quiet=False):
1637 cmd = ['checkout']
1638 if quiet:
1639 cmd.append('-q')
1640 cmd.append(rev)
1641 cmd.append('--')
1642 if GitCommand(self, cmd).Wait() != 0:
1643 if self._allrefs:
1644 raise GitError('%s checkout %s ' % (self.name, rev))
1645
1646 def _ResetHard(self, rev, quiet=True):
1647 cmd = ['reset', '--hard']
1648 if quiet:
1649 cmd.append('-q')
1650 cmd.append(rev)
1651 if GitCommand(self, cmd).Wait() != 0:
1652 raise GitError('%s reset --hard %s ' % (self.name, rev))
1653
1654 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001655 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 if onto is not None:
1657 cmd.extend(['--onto', onto])
1658 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001659 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 raise GitError('%s rebase %s ' % (self.name, upstream))
1661
1662 def _FastForward(self, head):
1663 cmd = ['merge', head]
1664 if GitCommand(self, cmd).Wait() != 0:
1665 raise GitError('%s merge %s ' % (self.name, head))
1666
1667 def _InitGitDir(self):
1668 if not os.path.exists(self.gitdir):
1669 os.makedirs(self.gitdir)
1670 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001671
Shawn O. Pearce88443382010-10-08 10:02:09 +02001672 mp = self.manifest.manifestProject
1673 ref_dir = mp.config.GetString('repo.reference')
1674
1675 if ref_dir:
1676 mirror_git = os.path.join(ref_dir, self.name + '.git')
1677 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1678 self.relpath + '.git')
1679
1680 if os.path.exists(mirror_git):
1681 ref_dir = mirror_git
1682
1683 elif os.path.exists(repo_git):
1684 ref_dir = repo_git
1685
1686 else:
1687 ref_dir = None
1688
1689 if ref_dir:
1690 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1691 os.path.join(ref_dir, 'objects') + '\n')
1692
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001693 if self.manifest.IsMirror:
1694 self.config.SetString('core.bare', 'true')
1695 else:
1696 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697
1698 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001699 try:
1700 to_rm = os.listdir(hooks)
1701 except OSError:
1702 to_rm = []
1703 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001705 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706
1707 m = self.manifest.manifestProject.config
1708 for key in ['user.name', 'user.email']:
1709 if m.Has(key, include_defaults = False):
1710 self.config.SetString(key, m.GetString(key))
1711
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001712 def _InitHooks(self):
1713 hooks = self._gitdir_path('hooks')
1714 if not os.path.exists(hooks):
1715 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001716 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001717 name = os.path.basename(stock_hook)
1718
Victor Boivie65e0f352011-04-18 11:23:29 +02001719 if name in ('commit-msg',) and not self.remote.review \
1720 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001721 # Don't install a Gerrit Code Review hook if this
1722 # project does not appear to use it for reviews.
1723 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001724 # Since the manifest project is one of those, but also
1725 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001726 continue
1727
1728 dst = os.path.join(hooks, name)
1729 if os.path.islink(dst):
1730 continue
1731 if os.path.exists(dst):
1732 if filecmp.cmp(stock_hook, dst, shallow=False):
1733 os.remove(dst)
1734 else:
1735 _error("%s: Not replacing %s hook", self.relpath, name)
1736 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001737 try:
1738 os.symlink(relpath(stock_hook, dst), dst)
1739 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001740 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001741 raise GitError('filesystem must support symlinks')
1742 else:
1743 raise
1744
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001746 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001748 remote.url = self.remote.url
1749 remote.review = self.remote.review
1750 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001752 if self.worktree:
1753 remote.ResetFetch(mirror=False)
1754 else:
1755 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756 remote.Save()
1757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 def _InitMRef(self):
1759 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001760 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001761
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001762 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001763 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001764
1765 def _InitAnyMRef(self, ref):
1766 cur = self.bare_ref.symref(ref)
1767
1768 if self.revisionId:
1769 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1770 msg = 'manifest set to %s' % self.revisionId
1771 dst = self.revisionId + '^0'
1772 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1773 else:
1774 remote = self.GetRemote(self.remote.name)
1775 dst = remote.ToLocal(self.revisionExpr)
1776 if cur != dst:
1777 msg = 'manifest set to %s' % self.revisionExpr
1778 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 def _InitWorkTree(self):
1781 dotgit = os.path.join(self.worktree, '.git')
1782 if not os.path.exists(dotgit):
1783 os.makedirs(dotgit)
1784
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785 for name in ['config',
1786 'description',
1787 'hooks',
1788 'info',
1789 'logs',
1790 'objects',
1791 'packed-refs',
1792 'refs',
1793 'rr-cache',
1794 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001795 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001796 src = os.path.join(self.gitdir, name)
1797 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001798 if os.path.islink(dst) or not os.path.exists(dst):
1799 os.symlink(relpath(src, dst), dst)
1800 else:
1801 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001802 except OSError, e:
1803 if e.errno == errno.EPERM:
1804 raise GitError('filesystem must support symlinks')
1805 else:
1806 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001808 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809
1810 cmd = ['read-tree', '--reset', '-u']
1811 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001812 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001813 if GitCommand(self, cmd).Wait() != 0:
1814 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001815
1816 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1817 if not os.path.exists(rr_cache):
1818 os.makedirs(rr_cache)
1819
Shawn O. Pearce93609662009-04-21 10:50:33 -07001820 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821
1822 def _gitdir_path(self, path):
1823 return os.path.join(self.gitdir, path)
1824
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001825 def _revlist(self, *args, **kw):
1826 a = []
1827 a.extend(args)
1828 a.append('--')
1829 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830
1831 @property
1832 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001833 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834
1835 class _GitGetByExec(object):
1836 def __init__(self, project, bare):
1837 self._project = project
1838 self._bare = bare
1839
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840 def LsOthers(self):
1841 p = GitCommand(self._project,
1842 ['ls-files',
1843 '-z',
1844 '--others',
1845 '--exclude-standard'],
1846 bare = False,
1847 capture_stdout = True,
1848 capture_stderr = True)
1849 if p.Wait() == 0:
1850 out = p.stdout
1851 if out:
1852 return out[:-1].split("\0")
1853 return []
1854
1855 def DiffZ(self, name, *args):
1856 cmd = [name]
1857 cmd.append('-z')
1858 cmd.extend(args)
1859 p = GitCommand(self._project,
1860 cmd,
1861 bare = False,
1862 capture_stdout = True,
1863 capture_stderr = True)
1864 try:
1865 out = p.process.stdout.read()
1866 r = {}
1867 if out:
1868 out = iter(out[:-1].split('\0'))
1869 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001870 try:
1871 info = out.next()
1872 path = out.next()
1873 except StopIteration:
1874 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001875
1876 class _Info(object):
1877 def __init__(self, path, omode, nmode, oid, nid, state):
1878 self.path = path
1879 self.src_path = None
1880 self.old_mode = omode
1881 self.new_mode = nmode
1882 self.old_id = oid
1883 self.new_id = nid
1884
1885 if len(state) == 1:
1886 self.status = state
1887 self.level = None
1888 else:
1889 self.status = state[:1]
1890 self.level = state[1:]
1891 while self.level.startswith('0'):
1892 self.level = self.level[1:]
1893
1894 info = info[1:].split(' ')
1895 info =_Info(path, *info)
1896 if info.status in ('R', 'C'):
1897 info.src_path = info.path
1898 info.path = out.next()
1899 r[info.path] = info
1900 return r
1901 finally:
1902 p.Wait()
1903
1904 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001905 if self._bare:
1906 path = os.path.join(self._project.gitdir, HEAD)
1907 else:
1908 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001909 fd = open(path, 'rb')
1910 try:
1911 line = fd.read()
1912 finally:
1913 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001914 if line.startswith('ref: '):
1915 return line[5:-1]
1916 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001917
1918 def SetHead(self, ref, message=None):
1919 cmdv = []
1920 if message is not None:
1921 cmdv.extend(['-m', message])
1922 cmdv.append(HEAD)
1923 cmdv.append(ref)
1924 self.symbolic_ref(*cmdv)
1925
1926 def DetachHead(self, new, message=None):
1927 cmdv = ['--no-deref']
1928 if message is not None:
1929 cmdv.extend(['-m', message])
1930 cmdv.append(HEAD)
1931 cmdv.append(new)
1932 self.update_ref(*cmdv)
1933
1934 def UpdateRef(self, name, new, old=None,
1935 message=None,
1936 detach=False):
1937 cmdv = []
1938 if message is not None:
1939 cmdv.extend(['-m', message])
1940 if detach:
1941 cmdv.append('--no-deref')
1942 cmdv.append(name)
1943 cmdv.append(new)
1944 if old is not None:
1945 cmdv.append(old)
1946 self.update_ref(*cmdv)
1947
1948 def DeleteRef(self, name, old=None):
1949 if not old:
1950 old = self.rev_parse(name)
1951 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001952 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001953
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001954 def rev_list(self, *args, **kw):
1955 if 'format' in kw:
1956 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1957 else:
1958 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001959 cmdv.extend(args)
1960 p = GitCommand(self._project,
1961 cmdv,
1962 bare = self._bare,
1963 capture_stdout = True,
1964 capture_stderr = True)
1965 r = []
1966 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001967 if line[-1] == '\n':
1968 line = line[:-1]
1969 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970 if p.Wait() != 0:
1971 raise GitError('%s rev-list %s: %s' % (
1972 self._project.name,
1973 str(args),
1974 p.stderr))
1975 return r
1976
1977 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001978 """Allow arbitrary git commands using pythonic syntax.
1979
1980 This allows you to do things like:
1981 git_obj.rev_parse('HEAD')
1982
1983 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1984 run. We'll replace the '_' with a '-' and try to run a git command.
1985 Any other arguments will be passed to the git command.
1986
1987 Args:
1988 name: The name of the git command to call. Any '_' characters will
1989 be replaced with '-'.
1990
1991 Returns:
1992 A callable object that will try to call git with the named command.
1993 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001994 name = name.replace('_', '-')
1995 def runner(*args):
1996 cmdv = [name]
1997 cmdv.extend(args)
1998 p = GitCommand(self._project,
1999 cmdv,
2000 bare = self._bare,
2001 capture_stdout = True,
2002 capture_stderr = True)
2003 if p.Wait() != 0:
2004 raise GitError('%s %s: %s' % (
2005 self._project.name,
2006 name,
2007 p.stderr))
2008 r = p.stdout
2009 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2010 return r[:-1]
2011 return r
2012 return runner
2013
2014
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002015class _PriorSyncFailedError(Exception):
2016 def __str__(self):
2017 return 'prior sync failed; rebase still in progress'
2018
2019class _DirtyError(Exception):
2020 def __str__(self):
2021 return 'contains uncommitted changes'
2022
2023class _InfoMessage(object):
2024 def __init__(self, project, text):
2025 self.project = project
2026 self.text = text
2027
2028 def Print(self, syncbuf):
2029 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2030 syncbuf.out.nl()
2031
2032class _Failure(object):
2033 def __init__(self, project, why):
2034 self.project = project
2035 self.why = why
2036
2037 def Print(self, syncbuf):
2038 syncbuf.out.fail('error: %s/: %s',
2039 self.project.relpath,
2040 str(self.why))
2041 syncbuf.out.nl()
2042
2043class _Later(object):
2044 def __init__(self, project, action):
2045 self.project = project
2046 self.action = action
2047
2048 def Run(self, syncbuf):
2049 out = syncbuf.out
2050 out.project('project %s/', self.project.relpath)
2051 out.nl()
2052 try:
2053 self.action()
2054 out.nl()
2055 return True
2056 except GitError, e:
2057 out.nl()
2058 return False
2059
2060class _SyncColoring(Coloring):
2061 def __init__(self, config):
2062 Coloring.__init__(self, config, 'reposync')
2063 self.project = self.printer('header', attr = 'bold')
2064 self.info = self.printer('info')
2065 self.fail = self.printer('fail', fg='red')
2066
2067class SyncBuffer(object):
2068 def __init__(self, config, detach_head=False):
2069 self._messages = []
2070 self._failures = []
2071 self._later_queue1 = []
2072 self._later_queue2 = []
2073
2074 self.out = _SyncColoring(config)
2075 self.out.redirect(sys.stderr)
2076
2077 self.detach_head = detach_head
2078 self.clean = True
2079
2080 def info(self, project, fmt, *args):
2081 self._messages.append(_InfoMessage(project, fmt % args))
2082
2083 def fail(self, project, err=None):
2084 self._failures.append(_Failure(project, err))
2085 self.clean = False
2086
2087 def later1(self, project, what):
2088 self._later_queue1.append(_Later(project, what))
2089
2090 def later2(self, project, what):
2091 self._later_queue2.append(_Later(project, what))
2092
2093 def Finish(self):
2094 self._PrintMessages()
2095 self._RunLater()
2096 self._PrintMessages()
2097 return self.clean
2098
2099 def _RunLater(self):
2100 for q in ['_later_queue1', '_later_queue2']:
2101 if not self._RunQueue(q):
2102 return
2103
2104 def _RunQueue(self, queue):
2105 for m in getattr(self, queue):
2106 if not m.Run(self):
2107 self.clean = False
2108 return False
2109 setattr(self, queue, [])
2110 return True
2111
2112 def _PrintMessages(self):
2113 for m in self._messages:
2114 m.Print(self)
2115 for m in self._failures:
2116 m.Print(self)
2117
2118 self._messages = []
2119 self._failures = []
2120
2121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122class MetaProject(Project):
2123 """A special project housed under .repo.
2124 """
2125 def __init__(self, manifest, name, gitdir, worktree):
2126 repodir = manifest.repodir
2127 Project.__init__(self,
2128 manifest = manifest,
2129 name = name,
2130 gitdir = gitdir,
2131 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002132 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002134 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002135 revisionId = None,
2136 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137
2138 def PreSync(self):
2139 if self.Exists:
2140 cb = self.CurrentBranch
2141 if cb:
2142 base = self.GetBranch(cb).merge
2143 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002144 self.revisionExpr = base
2145 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146
2147 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002148 def LastFetch(self):
2149 try:
2150 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2151 return os.path.getmtime(fh)
2152 except OSError:
2153 return 0
2154
2155 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002156 def HasChanges(self):
2157 """Has the remote received new commits not yet checked out?
2158 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002159 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002160 return False
2161
2162 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002163 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002164 head = self.work_git.GetHead()
2165 if head.startswith(R_HEADS):
2166 try:
2167 head = all[head]
2168 except KeyError:
2169 head = None
2170
2171 if revid == head:
2172 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002173 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002174 return True
2175 return False