blob: f48472b477be8df9eff6f9822a64d5a8be51df74 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Anderson37282b42011-03-04 11:54:18 -080015import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
18import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
23import sys
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070024import time
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import urllib2
26
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070027try:
28 import threading as _threading
29except ImportError:
30 import dummy_threading as _threading
31
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070032try:
33 from os import SEEK_END
34except ImportError:
35 SEEK_END = 2
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037from color import Coloring
38from git_command import GitCommand
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070039from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070040from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080041from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080042from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070043from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070047_urllib_lock = _threading.Lock()
48
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070049def _lwrite(path, content):
50 lock = '%s.lock' % path
51
52 fd = open(lock, 'wb')
53 try:
54 fd.write(content)
55 finally:
56 fd.close()
57
58 try:
59 os.rename(lock, path)
60 except OSError:
61 os.remove(lock)
62 raise
63
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
66 print >>sys.stderr, 'error: %s' % msg
67
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068def not_rev(r):
69 return '^' + r
70
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080071def sq(r):
72 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080073
Doug Anderson8ced8642011-01-10 14:16:30 -080074_project_hook_list = None
75def _ProjectHooks():
76 """List the hooks present in the 'hooks' directory.
77
78 These hooks are project hooks and are copied to the '.git/hooks' directory
79 of all subprojects.
80
81 This function caches the list of hooks (based on the contents of the
82 'repo/hooks' directory) on the first call.
83
84 Returns:
85 A list of absolute paths to all of the files in the hooks directory.
86 """
87 global _project_hook_list
88 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089 d = os.path.abspath(os.path.dirname(__file__))
90 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080091 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
92 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080093
94def relpath(dst, src):
95 src = os.path.dirname(src)
96 top = os.path.commonprefix([dst, src])
97 if top.endswith('/'):
98 top = top[:-1]
99 else:
100 top = os.path.dirname(top)
101
102 tmp = src
103 rel = ''
104 while top != tmp:
105 rel += '../'
106 tmp = os.path.dirname(tmp)
107 return rel + dst[len(top) + 1:]
108
109
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700110class DownloadedChange(object):
111 _commit_cache = None
112
113 def __init__(self, project, base, change_id, ps_id, commit):
114 self.project = project
115 self.base = base
116 self.change_id = change_id
117 self.ps_id = ps_id
118 self.commit = commit
119
120 @property
121 def commits(self):
122 if self._commit_cache is None:
123 self._commit_cache = self.project.bare_git.rev_list(
124 '--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
150 self._commit_cache = self.project.bare_git.rev_list(
151 '--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
164 for commit in self.project.bare_git.rev_list(
165 not_rev(self.base),
166 R_HEADS + self.name,
167 '--'):
168 r[commit[0:8]] = commit
169 return r
170
171 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 def date(self):
173 return self.project.bare_git.log(
174 '--pretty=format:%cd',
175 '-n', '1',
176 R_HEADS + self.name,
177 '--')
178
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700179 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
182 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700184 def GetPublishedRefs(self):
185 refs = {}
186 output = self.project.bare_git.ls_remote(
187 self.branch.remote.SshReviewUrl(self.project.UserEmail),
188 'refs/changes/*')
189 for line in output.split('\n'):
190 try:
191 (sha, ref) = line.split()
192 refs[sha] = ref
193 except ValueError:
194 pass
195
196 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
198class StatusColoring(Coloring):
199 def __init__(self, config):
200 Coloring.__init__(self, config, 'status')
201 self.project = self.printer('header', attr = 'bold')
202 self.branch = self.printer('header', attr = 'bold')
203 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700204 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206 self.added = self.printer('added', fg = 'green')
207 self.changed = self.printer('changed', fg = 'red')
208 self.untracked = self.printer('untracked', fg = 'red')
209
210
211class DiffColoring(Coloring):
212 def __init__(self, config):
213 Coloring.__init__(self, config, 'diff')
214 self.project = self.printer('header', attr = 'bold')
215
216
217class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800218 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219 self.src = src
220 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800221 self.abs_src = abssrc
222 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
224 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800225 src = self.abs_src
226 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 # copy file if it does not exist or is out of date
228 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
229 try:
230 # remove existing file first, since it might be read-only
231 if os.path.exists(dest):
232 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400233 else:
234 dir = os.path.dirname(dest)
235 if not os.path.isdir(dir):
236 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 shutil.copy(src, dest)
238 # make the file read-only
239 mode = os.stat(dest)[stat.ST_MODE]
240 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
241 os.chmod(dest, mode)
242 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700243 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700245class RemoteSpec(object):
246 def __init__(self,
247 name,
248 url = None,
249 review = None):
250 self.name = name
251 self.url = url
252 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Doug Anderson37282b42011-03-04 11:54:18 -0800254class RepoHook(object):
255 """A RepoHook contains information about a script to run as a hook.
256
257 Hooks are used to run a python script before running an upload (for instance,
258 to run presubmit checks). Eventually, we may have hooks for other actions.
259
260 This shouldn't be confused with files in the 'repo/hooks' directory. Those
261 files are copied into each '.git/hooks' folder for each project. Repo-level
262 hooks are associated instead with repo actions.
263
264 Hooks are always python. When a hook is run, we will load the hook into the
265 interpreter and execute its main() function.
266 """
267 def __init__(self,
268 hook_type,
269 hooks_project,
270 topdir,
271 abort_if_user_denies=False):
272 """RepoHook constructor.
273
274 Params:
275 hook_type: A string representing the type of hook. This is also used
276 to figure out the name of the file containing the hook. For
277 example: 'pre-upload'.
278 hooks_project: The project containing the repo hooks. If you have a
279 manifest, this is manifest.repo_hooks_project. OK if this is None,
280 which will make the hook a no-op.
281 topdir: Repo's top directory (the one containing the .repo directory).
282 Scripts will run with CWD as this directory. If you have a manifest,
283 this is manifest.topdir
284 abort_if_user_denies: If True, we'll throw a HookError() if the user
285 doesn't allow us to run the hook.
286 """
287 self._hook_type = hook_type
288 self._hooks_project = hooks_project
289 self._topdir = topdir
290 self._abort_if_user_denies = abort_if_user_denies
291
292 # Store the full path to the script for convenience.
293 if self._hooks_project:
294 self._script_fullpath = os.path.join(self._hooks_project.worktree,
295 self._hook_type + '.py')
296 else:
297 self._script_fullpath = None
298
299 def _GetHash(self):
300 """Return a hash of the contents of the hooks directory.
301
302 We'll just use git to do this. This hash has the property that if anything
303 changes in the directory we will return a different has.
304
305 SECURITY CONSIDERATION:
306 This hash only represents the contents of files in the hook directory, not
307 any other files imported or called by hooks. Changes to imported files
308 can change the script behavior without affecting the hash.
309
310 Returns:
311 A string representing the hash. This will always be ASCII so that it can
312 be printed to the user easily.
313 """
314 assert self._hooks_project, "Must have hooks to calculate their hash."
315
316 # We will use the work_git object rather than just calling GetRevisionId().
317 # That gives us a hash of the latest checked in version of the files that
318 # the user will actually be executing. Specifically, GetRevisionId()
319 # doesn't appear to change even if a user checks out a different version
320 # of the hooks repo (via git checkout) nor if a user commits their own revs.
321 #
322 # NOTE: Local (non-committed) changes will not be factored into this hash.
323 # I think this is OK, since we're really only worried about warning the user
324 # about upstream changes.
325 return self._hooks_project.work_git.rev_parse('HEAD')
326
327 def _GetMustVerb(self):
328 """Return 'must' if the hook is required; 'should' if not."""
329 if self._abort_if_user_denies:
330 return 'must'
331 else:
332 return 'should'
333
334 def _CheckForHookApproval(self):
335 """Check to see whether this hook has been approved.
336
337 We'll look at the hash of all of the hooks. If this matches the hash that
338 the user last approved, we're done. If it doesn't, we'll ask the user
339 about approval.
340
341 Note that we ask permission for each individual hook even though we use
342 the hash of all hooks when detecting changes. We'd like the user to be
343 able to approve / deny each hook individually. We only use the hash of all
344 hooks because there is no other easy way to detect changes to local imports.
345
346 Returns:
347 True if this hook is approved to run; False otherwise.
348
349 Raises:
350 HookError: Raised if the user doesn't approve and abort_if_user_denies
351 was passed to the consturctor.
352 """
353 hooks_dir = self._hooks_project.worktree
354 hooks_config = self._hooks_project.config
355 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
356
357 # Get the last hash that the user approved for this hook; may be None.
358 old_hash = hooks_config.GetString(git_approval_key)
359
360 # Get the current hash so we can tell if scripts changed since approval.
361 new_hash = self._GetHash()
362
363 if old_hash is not None:
364 # User previously approved hook and asked not to be prompted again.
365 if new_hash == old_hash:
366 # Approval matched. We're done.
367 return True
368 else:
369 # Give the user a reason why we're prompting, since they last told
370 # us to "never ask again".
371 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
372 self._hook_type)
373 else:
374 prompt = ''
375
376 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
377 if sys.stdout.isatty():
378 prompt += ('Repo %s run the script:\n'
379 ' %s\n'
380 '\n'
381 'Do you want to allow this script to run '
382 '(yes/yes-never-ask-again/NO)? ') % (
383 self._GetMustVerb(), self._script_fullpath)
384 response = raw_input(prompt).lower()
385 print
386
387 # User is doing a one-time approval.
388 if response in ('y', 'yes'):
389 return True
390 elif response == 'yes-never-ask-again':
391 hooks_config.SetString(git_approval_key, new_hash)
392 return True
393
394 # For anything else, we'll assume no approval.
395 if self._abort_if_user_denies:
396 raise HookError('You must allow the %s hook or use --no-verify.' %
397 self._hook_type)
398
399 return False
400
401 def _ExecuteHook(self, **kwargs):
402 """Actually execute the given hook.
403
404 This will run the hook's 'main' function in our python interpreter.
405
406 Args:
407 kwargs: Keyword arguments to pass to the hook. These are often specific
408 to the hook type. For instance, pre-upload hooks will contain
409 a project_list.
410 """
411 # Keep sys.path and CWD stashed away so that we can always restore them
412 # upon function exit.
413 orig_path = os.getcwd()
414 orig_syspath = sys.path
415
416 try:
417 # Always run hooks with CWD as topdir.
418 os.chdir(self._topdir)
419
420 # Put the hook dir as the first item of sys.path so hooks can do
421 # relative imports. We want to replace the repo dir as [0] so
422 # hooks can't import repo files.
423 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
424
425 # Exec, storing global context in the context dict. We catch exceptions
426 # and convert to a HookError w/ just the failing traceback.
427 context = {}
428 try:
429 execfile(self._script_fullpath, context)
430 except Exception:
431 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
432 traceback.format_exc(), self._hook_type))
433
434 # Running the script should have defined a main() function.
435 if 'main' not in context:
436 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
437
438
439 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
440 # We don't actually want hooks to define their main with this argument--
441 # it's there to remind them that their hook should always take **kwargs.
442 # For instance, a pre-upload hook should be defined like:
443 # def main(project_list, **kwargs):
444 #
445 # This allows us to later expand the API without breaking old hooks.
446 kwargs = kwargs.copy()
447 kwargs['hook_should_take_kwargs'] = True
448
449 # Call the main function in the hook. If the hook should cause the
450 # build to fail, it will raise an Exception. We'll catch that convert
451 # to a HookError w/ just the failing traceback.
452 try:
453 context['main'](**kwargs)
454 except Exception:
455 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
456 'above.' % (
457 traceback.format_exc(), self._hook_type))
458 finally:
459 # Restore sys.path and CWD.
460 sys.path = orig_syspath
461 os.chdir(orig_path)
462
463 def Run(self, user_allows_all_hooks, **kwargs):
464 """Run the hook.
465
466 If the hook doesn't exist (because there is no hooks project or because
467 this particular hook is not enabled), this is a no-op.
468
469 Args:
470 user_allows_all_hooks: If True, we will never prompt about running the
471 hook--we'll just assume it's OK to run it.
472 kwargs: Keyword arguments to pass to the hook. These are often specific
473 to the hook type. For instance, pre-upload hooks will contain
474 a project_list.
475
476 Raises:
477 HookError: If there was a problem finding the hook or the user declined
478 to run a required hook (from _CheckForHookApproval).
479 """
480 # No-op if there is no hooks project or if hook is disabled.
481 if ((not self._hooks_project) or
482 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
483 return
484
485 # Bail with a nice error if we can't find the hook.
486 if not os.path.isfile(self._script_fullpath):
487 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
488
489 # Make sure the user is OK with running the hook.
490 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
491 return
492
493 # Run the hook with the same version of python we're using.
494 self._ExecuteHook(**kwargs)
495
496
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700497class Project(object):
498 def __init__(self,
499 manifest,
500 name,
501 remote,
502 gitdir,
503 worktree,
504 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700505 revisionExpr,
506 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700507 self.manifest = manifest
508 self.name = name
509 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800510 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800511 if worktree:
512 self.worktree = worktree.replace('\\', '/')
513 else:
514 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700516 self.revisionExpr = revisionExpr
517
518 if revisionId is None \
519 and revisionExpr \
520 and IsId(revisionExpr):
521 self.revisionId = revisionExpr
522 else:
523 self.revisionId = revisionId
524
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 self.copyfiles = []
527 self.config = GitConfig.ForRepository(
528 gitdir = self.gitdir,
529 defaults = self.manifest.globalConfig)
530
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800531 if self.worktree:
532 self.work_git = self._GitGetByExec(self, bare=False)
533 else:
534 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700535 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700536 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537
Doug Anderson37282b42011-03-04 11:54:18 -0800538 # This will be filled in if a project is later identified to be the
539 # project containing repo hooks.
540 self.enabled_repo_hooks = []
541
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 @property
543 def Exists(self):
544 return os.path.isdir(self.gitdir)
545
546 @property
547 def CurrentBranch(self):
548 """Obtain the name of the currently checked out branch.
549 The branch name omits the 'refs/heads/' prefix.
550 None is returned if the project is on a detached HEAD.
551 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700552 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 if b.startswith(R_HEADS):
554 return b[len(R_HEADS):]
555 return None
556
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700557 def IsRebaseInProgress(self):
558 w = self.worktree
559 g = os.path.join(w, '.git')
560 return os.path.exists(os.path.join(g, 'rebase-apply')) \
561 or os.path.exists(os.path.join(g, 'rebase-merge')) \
562 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def IsDirty(self, consider_untracked=True):
565 """Is the working directory modified in some way?
566 """
567 self.work_git.update_index('-q',
568 '--unmerged',
569 '--ignore-missing',
570 '--refresh')
571 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
572 return True
573 if self.work_git.DiffZ('diff-files'):
574 return True
575 if consider_untracked and self.work_git.LsOthers():
576 return True
577 return False
578
579 _userident_name = None
580 _userident_email = None
581
582 @property
583 def UserName(self):
584 """Obtain the user's personal name.
585 """
586 if self._userident_name is None:
587 self._LoadUserIdentity()
588 return self._userident_name
589
590 @property
591 def UserEmail(self):
592 """Obtain the user's email address. This is very likely
593 to be their Gerrit login.
594 """
595 if self._userident_email is None:
596 self._LoadUserIdentity()
597 return self._userident_email
598
599 def _LoadUserIdentity(self):
600 u = self.bare_git.var('GIT_COMMITTER_IDENT')
601 m = re.compile("^(.*) <([^>]*)> ").match(u)
602 if m:
603 self._userident_name = m.group(1)
604 self._userident_email = m.group(2)
605 else:
606 self._userident_name = ''
607 self._userident_email = ''
608
609 def GetRemote(self, name):
610 """Get the configuration for a single remote.
611 """
612 return self.config.GetRemote(name)
613
614 def GetBranch(self, name):
615 """Get the configuration for a single branch.
616 """
617 return self.config.GetBranch(name)
618
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700619 def GetBranches(self):
620 """Get all existing local branches.
621 """
622 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700623 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700624 heads = {}
625 pubd = {}
626
627 for name, id in all.iteritems():
628 if name.startswith(R_HEADS):
629 name = name[len(R_HEADS):]
630 b = self.GetBranch(name)
631 b.current = name == current
632 b.published = None
633 b.revision = id
634 heads[name] = b
635
636 for name, id in all.iteritems():
637 if name.startswith(R_PUB):
638 name = name[len(R_PUB):]
639 b = heads.get(name)
640 if b:
641 b.published = id
642
643 return heads
644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646## Status Display ##
647
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500648 def HasChanges(self):
649 """Returns true if there are uncommitted changes.
650 """
651 self.work_git.update_index('-q',
652 '--unmerged',
653 '--ignore-missing',
654 '--refresh')
655 if self.IsRebaseInProgress():
656 return True
657
658 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
659 return True
660
661 if self.work_git.DiffZ('diff-files'):
662 return True
663
664 if self.work_git.LsOthers():
665 return True
666
667 return False
668
Terence Haddock4655e812011-03-31 12:33:34 +0200669 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700670 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200671
672 Args:
673 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 """
675 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200676 if output_redir == None:
677 output_redir = sys.stdout
678 print >>output_redir, ''
679 print >>output_redir, 'project %s/' % self.relpath
680 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681 return
682
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700687 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
689 df = self.work_git.DiffZ('diff-files')
690 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700691 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700692 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693
694 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200695 if not output_redir == None:
696 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 out.project('project %-40s', self.relpath + '/')
698
699 branch = self.CurrentBranch
700 if branch is None:
701 out.nobranch('(*** NO BRANCH ***)')
702 else:
703 out.branch('branch %s', branch)
704 out.nl()
705
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700706 if rb:
707 out.important('prior sync failed; rebase still in progress')
708 out.nl()
709
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710 paths = list()
711 paths.extend(di.keys())
712 paths.extend(df.keys())
713 paths.extend(do)
714
715 paths = list(set(paths))
716 paths.sort()
717
718 for p in paths:
719 try: i = di[p]
720 except KeyError: i = None
721
722 try: f = df[p]
723 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 if i: i_status = i.status.upper()
726 else: i_status = '-'
727
728 if f: f_status = f.status.lower()
729 else: f_status = '-'
730
731 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800732 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 i.src_path, p, i.level)
734 else:
735 line = ' %s%s\t%s' % (i_status, f_status, p)
736
737 if i and not f:
738 out.added('%s', line)
739 elif (i and f) or (not i and f):
740 out.changed('%s', line)
741 elif not i and not f:
742 out.untracked('%s', line)
743 else:
744 out.write('%s', line)
745 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200746
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700747 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749 def PrintWorkTreeDiff(self):
750 """Prints the status of the repository to stdout.
751 """
752 out = DiffColoring(self.config)
753 cmd = ['diff']
754 if out.is_on:
755 cmd.append('--color')
756 cmd.append(HEAD)
757 cmd.append('--')
758 p = GitCommand(self,
759 cmd,
760 capture_stdout = True,
761 capture_stderr = True)
762 has_diff = False
763 for line in p.process.stdout:
764 if not has_diff:
765 out.nl()
766 out.project('project %s/' % self.relpath)
767 out.nl()
768 has_diff = True
769 print line[:-1]
770 p.Wait()
771
772
773## Publish / Upload ##
774
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700775 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 """Was the branch published (uploaded) for code review?
777 If so, returns the SHA-1 hash of the last published
778 state for the branch.
779 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700780 key = R_PUB + branch
781 if all is None:
782 try:
783 return self.bare_git.rev_parse(key)
784 except GitError:
785 return None
786 else:
787 try:
788 return all[key]
789 except KeyError:
790 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700792 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793 """Prunes any stale published refs.
794 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700795 if all is None:
796 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 heads = set()
798 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700799 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 if name.startswith(R_HEADS):
801 heads.add(name)
802 elif name.startswith(R_PUB):
803 canrm[name] = id
804
805 for name, id in canrm.iteritems():
806 n = name[len(R_PUB):]
807 if R_HEADS + n not in heads:
808 self.bare_git.DeleteRef(name, id)
809
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700810 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 """List any branches which can be uploaded for review.
812 """
813 heads = {}
814 pubed = {}
815
816 for name, id in self._allrefs.iteritems():
817 if name.startswith(R_HEADS):
818 heads[name[len(R_HEADS):]] = id
819 elif name.startswith(R_PUB):
820 pubed[name[len(R_PUB):]] = id
821
822 ready = []
823 for branch, id in heads.iteritems():
824 if branch in pubed and pubed[branch] == id:
825 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700826 if selected_branch and branch != selected_branch:
827 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800829 rb = self.GetUploadableBranch(branch)
830 if rb:
831 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 return ready
833
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800834 def GetUploadableBranch(self, branch_name):
835 """Get a single uploadable branch, or None.
836 """
837 branch = self.GetBranch(branch_name)
838 base = branch.LocalMerge
839 if branch.LocalMerge:
840 rb = ReviewableBranch(self, branch, base)
841 if rb.commits:
842 return rb
843 return None
844
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700845 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700846 people=([],[]),
847 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 """Uploads the named branch for code review.
849 """
850 if branch is None:
851 branch = self.CurrentBranch
852 if branch is None:
853 raise GitError('not currently on a branch')
854
855 branch = self.GetBranch(branch)
856 if not branch.LocalMerge:
857 raise GitError('branch %s does not track a remote' % branch.name)
858 if not branch.remote.review:
859 raise GitError('remote %s has no review url' % branch.remote.name)
860
861 dest_branch = branch.merge
862 if not dest_branch.startswith(R_HEADS):
863 dest_branch = R_HEADS + dest_branch
864
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800865 if not branch.remote.projectname:
866 branch.remote.projectname = self.name
867 branch.remote.Save()
868
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800869 url = branch.remote.ReviewUrl(self.UserEmail)
870 if url is None:
871 raise UploadError('review not configured')
872 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800873
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800874 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800875 rp = ['gerrit receive-pack']
876 for e in people[0]:
877 rp.append('--reviewer=%s' % sq(e))
878 for e in people[1]:
879 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800880 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700881
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800882 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800883
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800884 if dest_branch.startswith(R_HEADS):
885 dest_branch = dest_branch[len(R_HEADS):]
886 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
887 if auto_topic:
888 ref_spec = ref_spec + '/' + branch.name
889 cmd.append(ref_spec)
890
891 if GitCommand(self, cmd, bare = True).Wait() != 0:
892 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893
894 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
895 self.bare_git.UpdateRef(R_PUB + branch.name,
896 R_HEADS + branch.name,
897 message = msg)
898
899
900## Sync ##
901
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700902 def Sync_NetworkHalf(self, quiet=False, is_new=None, current_branch_only=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903 """Perform only the network IO portion of the sync process.
904 Local working directory/branch state is not affected.
905 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700906 if is_new is None:
907 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200908 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 self._InitGitDir()
910 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700911
912 if is_new:
913 alt = os.path.join(self.gitdir, 'objects/info/alternates')
914 try:
915 fd = open(alt, 'rb')
916 try:
917 alt_dir = fd.readline().rstrip()
918 finally:
919 fd.close()
920 except IOError:
921 alt_dir = None
922 else:
923 alt_dir = None
924
925 if alt_dir is None and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
926 is_new = False
927
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700928 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
929 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800931
932 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800933 self._InitMRef()
934 else:
935 self._InitMirrorHead()
936 try:
937 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
938 except OSError:
939 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800941
942 def PostRepoUpgrade(self):
943 self._InitHooks()
944
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 def _CopyFiles(self):
946 for file in self.copyfiles:
947 file._Copy()
948
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700949 def GetRevisionId(self, all=None):
950 if self.revisionId:
951 return self.revisionId
952
953 rem = self.GetRemote(self.remote.name)
954 rev = rem.ToLocal(self.revisionExpr)
955
956 if all is not None and rev in all:
957 return all[rev]
958
959 try:
960 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
961 except GitError:
962 raise ManifestInvalidRevisionError(
963 'revision %s in %s not found' % (self.revisionExpr,
964 self.name))
965
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700966 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 """Perform only the local IO portion of the sync process.
968 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700970 all = self.bare_ref.all
971 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700972 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800973
974 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700975 head = self.work_git.GetHead()
976 if head.startswith(R_HEADS):
977 branch = head[len(R_HEADS):]
978 try:
979 head = all[head]
980 except KeyError:
981 head = None
982 else:
983 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700985 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 # Currently on a detached HEAD. The user is assumed to
987 # not have any local modifications worth worrying about.
988 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700989 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700990 syncbuf.fail(self, _PriorSyncFailedError())
991 return
992
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700993 if head == revid:
994 # No changes; don't do anything further.
995 #
996 return
997
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700998 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001000 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001002 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001003 except GitError, e:
1004 syncbuf.fail(self, e)
1005 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001007 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001009 if head == revid:
1010 # No changes; don't do anything further.
1011 #
1012 return
1013
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001016 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001018 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001020 syncbuf.info(self,
1021 "leaving %s; does not track upstream",
1022 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001024 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001025 except GitError, e:
1026 syncbuf.fail(self, e)
1027 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001029 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001031 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001032 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001034 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 if not_merged:
1036 if upstream_gain:
1037 # The user has published this branch and some of those
1038 # commits are not yet merged upstream. We do not want
1039 # to rewrite the published commits so we punt.
1040 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001041 syncbuf.fail(self,
1042 "branch %s is published (but not merged) and is now %d commits behind"
1043 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001044 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001045 elif pub == head:
1046 # All published commits are merged, and thus we are a
1047 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001048 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001049 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001050 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001051 self._CopyFiles()
1052 syncbuf.later1(self, _doff)
1053 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001055 # Examine the local commits not in the remote. Find the
1056 # last one attributed to this user, if any.
1057 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001058 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001059 last_mine = None
1060 cnt_mine = 0
1061 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001062 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001063 if committer_email == self.UserEmail:
1064 last_mine = commit_id
1065 cnt_mine += 1
1066
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001067 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001068 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
1070 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 syncbuf.fail(self, _DirtyError())
1072 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001074 # If the upstream switched on us, warn the user.
1075 #
1076 if branch.merge != self.revisionExpr:
1077 if branch.merge and self.revisionExpr:
1078 syncbuf.info(self,
1079 'manifest switched %s...%s',
1080 branch.merge,
1081 self.revisionExpr)
1082 elif branch.merge:
1083 syncbuf.info(self,
1084 'manifest no longer tracks %s',
1085 branch.merge)
1086
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001087 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001089 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001091 syncbuf.info(self,
1092 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001093 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001095 branch.remote = self.GetRemote(self.remote.name)
1096 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 branch.Save()
1098
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001100 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001101 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 self._CopyFiles()
1103 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001104 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001106 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 self._CopyFiles()
1108 except GitError, e:
1109 syncbuf.fail(self, e)
1110 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001112 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 self._CopyFiles()
1115 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001117 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 # dest should already be an absolute path, but src is project relative
1119 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001120 abssrc = os.path.join(self.worktree, src)
1121 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001123 def DownloadPatchSet(self, change_id, patch_id):
1124 """Download a single patch set of a single change to FETCH_HEAD.
1125 """
1126 remote = self.GetRemote(self.remote.name)
1127
1128 cmd = ['fetch', remote.name]
1129 cmd.append('refs/changes/%2.2d/%d/%d' \
1130 % (change_id % 100, change_id, patch_id))
1131 cmd.extend(map(lambda x: str(x), remote.fetch))
1132 if GitCommand(self, cmd, bare=True).Wait() != 0:
1133 return None
1134 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001135 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001136 change_id,
1137 patch_id,
1138 self.bare_git.rev_parse('FETCH_HEAD'))
1139
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140
1141## Branch Management ##
1142
1143 def StartBranch(self, name):
1144 """Create a new branch off the manifest's revision.
1145 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001146 head = self.work_git.GetHead()
1147 if head == (R_HEADS + name):
1148 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001150 all = self.bare_ref.all
1151 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001152 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001153 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001154 capture_stdout = True,
1155 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001156
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001157 branch = self.GetBranch(name)
1158 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001159 branch.merge = self.revisionExpr
1160 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001161
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001162 if head.startswith(R_HEADS):
1163 try:
1164 head = all[head]
1165 except KeyError:
1166 head = None
1167
1168 if revid and head and revid == head:
1169 ref = os.path.join(self.gitdir, R_HEADS + name)
1170 try:
1171 os.makedirs(os.path.dirname(ref))
1172 except OSError:
1173 pass
1174 _lwrite(ref, '%s\n' % revid)
1175 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1176 'ref: %s%s\n' % (R_HEADS, name))
1177 branch.Save()
1178 return True
1179
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001180 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001181 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001182 capture_stdout = True,
1183 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001184 branch.Save()
1185 return True
1186 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
Wink Saville02d79452009-04-10 13:01:24 -07001188 def CheckoutBranch(self, name):
1189 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001190
1191 Args:
1192 name: The name of the branch to checkout.
1193
1194 Returns:
1195 True if the checkout succeeded; False if it didn't; None if the branch
1196 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001197 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001198 rev = R_HEADS + name
1199 head = self.work_git.GetHead()
1200 if head == rev:
1201 # Already on the branch
1202 #
1203 return True
Wink Saville02d79452009-04-10 13:01:24 -07001204
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001205 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001206 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001207 revid = all[rev]
1208 except KeyError:
1209 # Branch does not exist in this project
1210 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001211 return None
Wink Saville02d79452009-04-10 13:01:24 -07001212
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001213 if head.startswith(R_HEADS):
1214 try:
1215 head = all[head]
1216 except KeyError:
1217 head = None
1218
1219 if head == revid:
1220 # Same revision; just update HEAD to point to the new
1221 # target branch, but otherwise take no other action.
1222 #
1223 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1224 'ref: %s%s\n' % (R_HEADS, name))
1225 return True
1226
1227 return GitCommand(self,
1228 ['checkout', name, '--'],
1229 capture_stdout = True,
1230 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001231
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001232 def AbandonBranch(self, name):
1233 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001234
1235 Args:
1236 name: The name of the branch to abandon.
1237
1238 Returns:
1239 True if the abandon succeeded; False if it didn't; None if the branch
1240 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001241 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001242 rev = R_HEADS + name
1243 all = self.bare_ref.all
1244 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001245 # Doesn't exist
1246 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001247
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001248 head = self.work_git.GetHead()
1249 if head == rev:
1250 # We can't destroy the branch while we are sitting
1251 # on it. Switch to a detached HEAD.
1252 #
1253 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001254
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001255 revid = self.GetRevisionId(all)
1256 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001257 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1258 '%s\n' % revid)
1259 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001260 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001261
1262 return GitCommand(self,
1263 ['branch', '-D', name],
1264 capture_stdout = True,
1265 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001266
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 def PruneHeads(self):
1268 """Prune any topic branches already merged into upstream.
1269 """
1270 cb = self.CurrentBranch
1271 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001272 left = self._allrefs
1273 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 if name.startswith(R_HEADS):
1275 name = name[len(R_HEADS):]
1276 if cb is None or name != cb:
1277 kill.append(name)
1278
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001279 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 if cb is not None \
1281 and not self._revlist(HEAD + '...' + rev) \
1282 and not self.IsDirty(consider_untracked = False):
1283 self.work_git.DetachHead(HEAD)
1284 kill.append(cb)
1285
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001287 old = self.bare_git.GetHead()
1288 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 try:
1292 self.bare_git.DetachHead(rev)
1293
1294 b = ['branch', '-d']
1295 b.extend(kill)
1296 b = GitCommand(self, b, bare=True,
1297 capture_stdout=True,
1298 capture_stderr=True)
1299 b.Wait()
1300 finally:
1301 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001302 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001304 for branch in kill:
1305 if (R_HEADS + branch) not in left:
1306 self.CleanPublishedCache()
1307 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
1309 if cb and cb not in kill:
1310 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001311 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312
1313 kept = []
1314 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001315 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 branch = self.GetBranch(branch)
1317 base = branch.LocalMerge
1318 if not base:
1319 base = rev
1320 kept.append(ReviewableBranch(self, branch, base))
1321 return kept
1322
1323
1324## Direct Git Commands ##
1325
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001326 def _RemoteFetch(self, name=None,
1327 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001328 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001329 quiet=False,
1330 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001331
1332 is_sha1 = False
1333 tag_name = None
1334
1335 if current_branch_only:
1336 if ID_RE.match(self.revisionExpr) is not None:
1337 is_sha1 = True
1338 elif self.revisionExpr.startswith(R_TAGS):
1339 # this is a tag and its sha1 value should never change
1340 tag_name = self.revisionExpr[len(R_TAGS):]
1341
1342 if is_sha1 or tag_name is not None:
1343 try:
1344 self.GetRevisionId()
1345 return True
1346 except ManifestInvalidRevisionError:
1347 # There is no such persistent revision. We have to fetch it.
1348 pass
1349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350 if not name:
1351 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001352
1353 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001354 remote = self.GetRemote(name)
1355 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001356 ssh_proxy = True
1357
Shawn O. Pearce88443382010-10-08 10:02:09 +02001358 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001359 if alt_dir and 'objects' == os.path.basename(alt_dir):
1360 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001361 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1362 remote = self.GetRemote(name)
1363
1364 all = self.bare_ref.all
1365 ids = set(all.values())
1366 tmp = set()
1367
1368 for r, id in GitRefs(ref_dir).all.iteritems():
1369 if r not in all:
1370 if r.startswith(R_TAGS) or remote.WritesTo(r):
1371 all[r] = id
1372 ids.add(id)
1373 continue
1374
1375 if id in ids:
1376 continue
1377
1378 r = 'refs/_alt/%s' % id
1379 all[r] = id
1380 ids.add(id)
1381 tmp.add(r)
1382
1383 ref_names = list(all.keys())
1384 ref_names.sort()
1385
1386 tmp_packed = ''
1387 old_packed = ''
1388
1389 for r in ref_names:
1390 line = '%s %s\n' % (all[r], r)
1391 tmp_packed += line
1392 if r not in tmp:
1393 old_packed += line
1394
1395 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001396 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001397 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001398
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001399 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001400
1401 # The --depth option only affects the initial fetch; after that we'll do
1402 # full fetches of changes.
1403 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1404 if depth and initial:
1405 cmd.append('--depth=%s' % depth)
1406
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001407 if quiet:
1408 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001409 if not self.worktree:
1410 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001411 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001412
1413 if not current_branch_only or is_sha1:
1414 # Fetch whole repo
1415 cmd.append('--tags')
1416 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1417 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001418 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001419 cmd.append(tag_name)
1420 else:
1421 branch = self.revisionExpr
1422 if branch.startswith(R_HEADS):
1423 branch = branch[len(R_HEADS):]
1424 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001425
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001426 ok = False
1427 for i in range(2):
1428 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1429 ok = True
1430 break
1431 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001432
1433 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001434 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001435 if old_packed != '':
1436 _lwrite(packed_refs, old_packed)
1437 else:
1438 os.remove(packed_refs)
1439 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001440 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001441
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001442 def _ApplyCloneBundle(self, initial=False, quiet=False):
1443 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1444 return False
1445
1446 remote = self.GetRemote(self.remote.name)
1447 bundle_url = remote.url + '/clone.bundle'
1448 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1449 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1450 return False
1451
1452 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1453 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1454
1455 exist_dst = os.path.exists(bundle_dst)
1456 exist_tmp = os.path.exists(bundle_tmp)
1457
1458 if not initial and not exist_dst and not exist_tmp:
1459 return False
1460
1461 if not exist_dst:
1462 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1463 if not exist_dst:
1464 return False
1465
1466 cmd = ['fetch']
1467 if quiet:
1468 cmd.append('--quiet')
1469 if not self.worktree:
1470 cmd.append('--update-head-ok')
1471 cmd.append(bundle_dst)
1472 for f in remote.fetch:
1473 cmd.append(str(f))
1474 cmd.append('refs/tags/*:refs/tags/*')
1475
1476 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001477 if os.path.exists(bundle_dst):
1478 os.remove(bundle_dst)
1479 if os.path.exists(bundle_tmp):
1480 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001481 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001482
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001484 keep = True
1485 done = False
1486 dest = open(tmpPath, 'a+b')
1487 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001488 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001489 pos = dest.tell()
1490
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001491 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001492 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001493 req = urllib2.Request(srcUrl)
1494 if pos > 0:
1495 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001496
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001497 try:
1498 r = urllib2.urlopen(req)
1499 except urllib2.HTTPError, e:
1500 def _content_type():
1501 try:
1502 return e.info()['content-type']
1503 except:
1504 return None
1505
1506 if e.code == 404:
1507 keep = False
1508 return False
1509 elif _content_type() == 'text/plain':
1510 try:
1511 msg = e.read()
1512 if len(msg) > 0 and msg[-1] == '\n':
1513 msg = msg[0:-1]
1514 msg = ' (%s)' % msg
1515 except:
1516 msg = ''
1517 else:
1518 try:
1519 from BaseHTTPServer import BaseHTTPRequestHandler
1520 res = BaseHTTPRequestHandler.responses[e.code]
1521 msg = ' (%s: %s)' % (res[0], res[1])
1522 except:
1523 msg = ''
1524 raise DownloadError('HTTP %s%s' % (e.code, msg))
1525 except urllib2.URLError, e:
1526 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1527 finally:
1528 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001529
1530 p = None
1531 try:
1532 size = r.headers['content-length']
1533 unit = 1 << 10
1534
1535 if size and not quiet:
1536 if size > 1024 * 1.3:
1537 unit = 1 << 20
1538 desc = 'MB'
1539 else:
1540 desc = 'KB'
1541 p = Progress(
1542 'Downloading %s' % self.relpath,
1543 int(size) / unit,
1544 units=desc)
1545 if pos > 0:
1546 p.update(pos / unit)
1547
1548 s = 0
1549 while True:
1550 d = r.read(8192)
1551 if d == '':
1552 done = True
1553 return True
1554 dest.write(d)
1555 if p:
1556 s += len(d)
1557 if s >= unit:
1558 p.update(s / unit)
1559 s = s % unit
1560 if p:
1561 if s >= unit:
1562 p.update(s / unit)
1563 else:
1564 p.update(1)
1565 finally:
1566 r.close()
1567 if p:
1568 p.end()
1569 finally:
1570 dest.close()
1571
1572 if os.path.exists(dstPath):
1573 os.remove(dstPath)
1574 if done:
1575 os.rename(tmpPath, dstPath)
1576 elif not keep:
1577 os.remove(tmpPath)
1578
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001579 def _Checkout(self, rev, quiet=False):
1580 cmd = ['checkout']
1581 if quiet:
1582 cmd.append('-q')
1583 cmd.append(rev)
1584 cmd.append('--')
1585 if GitCommand(self, cmd).Wait() != 0:
1586 if self._allrefs:
1587 raise GitError('%s checkout %s ' % (self.name, rev))
1588
1589 def _ResetHard(self, rev, quiet=True):
1590 cmd = ['reset', '--hard']
1591 if quiet:
1592 cmd.append('-q')
1593 cmd.append(rev)
1594 if GitCommand(self, cmd).Wait() != 0:
1595 raise GitError('%s reset --hard %s ' % (self.name, rev))
1596
1597 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001598 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599 if onto is not None:
1600 cmd.extend(['--onto', onto])
1601 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001602 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 raise GitError('%s rebase %s ' % (self.name, upstream))
1604
1605 def _FastForward(self, head):
1606 cmd = ['merge', head]
1607 if GitCommand(self, cmd).Wait() != 0:
1608 raise GitError('%s merge %s ' % (self.name, head))
1609
1610 def _InitGitDir(self):
1611 if not os.path.exists(self.gitdir):
1612 os.makedirs(self.gitdir)
1613 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001614
Shawn O. Pearce88443382010-10-08 10:02:09 +02001615 mp = self.manifest.manifestProject
1616 ref_dir = mp.config.GetString('repo.reference')
1617
1618 if ref_dir:
1619 mirror_git = os.path.join(ref_dir, self.name + '.git')
1620 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1621 self.relpath + '.git')
1622
1623 if os.path.exists(mirror_git):
1624 ref_dir = mirror_git
1625
1626 elif os.path.exists(repo_git):
1627 ref_dir = repo_git
1628
1629 else:
1630 ref_dir = None
1631
1632 if ref_dir:
1633 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1634 os.path.join(ref_dir, 'objects') + '\n')
1635
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001636 if self.manifest.IsMirror:
1637 self.config.SetString('core.bare', 'true')
1638 else:
1639 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001640
1641 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001642 try:
1643 to_rm = os.listdir(hooks)
1644 except OSError:
1645 to_rm = []
1646 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001647 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001648 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649
1650 m = self.manifest.manifestProject.config
1651 for key in ['user.name', 'user.email']:
1652 if m.Has(key, include_defaults = False):
1653 self.config.SetString(key, m.GetString(key))
1654
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001655 def _InitHooks(self):
1656 hooks = self._gitdir_path('hooks')
1657 if not os.path.exists(hooks):
1658 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001659 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001660 name = os.path.basename(stock_hook)
1661
Victor Boivie65e0f352011-04-18 11:23:29 +02001662 if name in ('commit-msg',) and not self.remote.review \
1663 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001664 # Don't install a Gerrit Code Review hook if this
1665 # project does not appear to use it for reviews.
1666 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001667 # Since the manifest project is one of those, but also
1668 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001669 continue
1670
1671 dst = os.path.join(hooks, name)
1672 if os.path.islink(dst):
1673 continue
1674 if os.path.exists(dst):
1675 if filecmp.cmp(stock_hook, dst, shallow=False):
1676 os.remove(dst)
1677 else:
1678 _error("%s: Not replacing %s hook", self.relpath, name)
1679 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001680 try:
1681 os.symlink(relpath(stock_hook, dst), dst)
1682 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001683 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001684 raise GitError('filesystem must support symlinks')
1685 else:
1686 raise
1687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001689 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001691 remote.url = self.remote.url
1692 remote.review = self.remote.review
1693 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001695 if self.worktree:
1696 remote.ResetFetch(mirror=False)
1697 else:
1698 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699 remote.Save()
1700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 def _InitMRef(self):
1702 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001703 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001705 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001706 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001707
1708 def _InitAnyMRef(self, ref):
1709 cur = self.bare_ref.symref(ref)
1710
1711 if self.revisionId:
1712 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1713 msg = 'manifest set to %s' % self.revisionId
1714 dst = self.revisionId + '^0'
1715 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1716 else:
1717 remote = self.GetRemote(self.remote.name)
1718 dst = remote.ToLocal(self.revisionExpr)
1719 if cur != dst:
1720 msg = 'manifest set to %s' % self.revisionExpr
1721 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001722
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723 def _InitWorkTree(self):
1724 dotgit = os.path.join(self.worktree, '.git')
1725 if not os.path.exists(dotgit):
1726 os.makedirs(dotgit)
1727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 for name in ['config',
1729 'description',
1730 'hooks',
1731 'info',
1732 'logs',
1733 'objects',
1734 'packed-refs',
1735 'refs',
1736 'rr-cache',
1737 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001738 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001739 src = os.path.join(self.gitdir, name)
1740 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001741 if os.path.islink(dst) or not os.path.exists(dst):
1742 os.symlink(relpath(src, dst), dst)
1743 else:
1744 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001745 except OSError, e:
1746 if e.errno == errno.EPERM:
1747 raise GitError('filesystem must support symlinks')
1748 else:
1749 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001751 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752
1753 cmd = ['read-tree', '--reset', '-u']
1754 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001755 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756 if GitCommand(self, cmd).Wait() != 0:
1757 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001758
1759 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1760 if not os.path.exists(rr_cache):
1761 os.makedirs(rr_cache)
1762
Shawn O. Pearce93609662009-04-21 10:50:33 -07001763 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764
1765 def _gitdir_path(self, path):
1766 return os.path.join(self.gitdir, path)
1767
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001768 def _revlist(self, *args, **kw):
1769 a = []
1770 a.extend(args)
1771 a.append('--')
1772 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773
1774 @property
1775 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001776 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
1778 class _GitGetByExec(object):
1779 def __init__(self, project, bare):
1780 self._project = project
1781 self._bare = bare
1782
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783 def LsOthers(self):
1784 p = GitCommand(self._project,
1785 ['ls-files',
1786 '-z',
1787 '--others',
1788 '--exclude-standard'],
1789 bare = False,
1790 capture_stdout = True,
1791 capture_stderr = True)
1792 if p.Wait() == 0:
1793 out = p.stdout
1794 if out:
1795 return out[:-1].split("\0")
1796 return []
1797
1798 def DiffZ(self, name, *args):
1799 cmd = [name]
1800 cmd.append('-z')
1801 cmd.extend(args)
1802 p = GitCommand(self._project,
1803 cmd,
1804 bare = False,
1805 capture_stdout = True,
1806 capture_stderr = True)
1807 try:
1808 out = p.process.stdout.read()
1809 r = {}
1810 if out:
1811 out = iter(out[:-1].split('\0'))
1812 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001813 try:
1814 info = out.next()
1815 path = out.next()
1816 except StopIteration:
1817 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001818
1819 class _Info(object):
1820 def __init__(self, path, omode, nmode, oid, nid, state):
1821 self.path = path
1822 self.src_path = None
1823 self.old_mode = omode
1824 self.new_mode = nmode
1825 self.old_id = oid
1826 self.new_id = nid
1827
1828 if len(state) == 1:
1829 self.status = state
1830 self.level = None
1831 else:
1832 self.status = state[:1]
1833 self.level = state[1:]
1834 while self.level.startswith('0'):
1835 self.level = self.level[1:]
1836
1837 info = info[1:].split(' ')
1838 info =_Info(path, *info)
1839 if info.status in ('R', 'C'):
1840 info.src_path = info.path
1841 info.path = out.next()
1842 r[info.path] = info
1843 return r
1844 finally:
1845 p.Wait()
1846
1847 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001848 if self._bare:
1849 path = os.path.join(self._project.gitdir, HEAD)
1850 else:
1851 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001852 fd = open(path, 'rb')
1853 try:
1854 line = fd.read()
1855 finally:
1856 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001857 if line.startswith('ref: '):
1858 return line[5:-1]
1859 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001860
1861 def SetHead(self, ref, message=None):
1862 cmdv = []
1863 if message is not None:
1864 cmdv.extend(['-m', message])
1865 cmdv.append(HEAD)
1866 cmdv.append(ref)
1867 self.symbolic_ref(*cmdv)
1868
1869 def DetachHead(self, new, message=None):
1870 cmdv = ['--no-deref']
1871 if message is not None:
1872 cmdv.extend(['-m', message])
1873 cmdv.append(HEAD)
1874 cmdv.append(new)
1875 self.update_ref(*cmdv)
1876
1877 def UpdateRef(self, name, new, old=None,
1878 message=None,
1879 detach=False):
1880 cmdv = []
1881 if message is not None:
1882 cmdv.extend(['-m', message])
1883 if detach:
1884 cmdv.append('--no-deref')
1885 cmdv.append(name)
1886 cmdv.append(new)
1887 if old is not None:
1888 cmdv.append(old)
1889 self.update_ref(*cmdv)
1890
1891 def DeleteRef(self, name, old=None):
1892 if not old:
1893 old = self.rev_parse(name)
1894 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001895 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001896
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001897 def rev_list(self, *args, **kw):
1898 if 'format' in kw:
1899 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1900 else:
1901 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001902 cmdv.extend(args)
1903 p = GitCommand(self._project,
1904 cmdv,
1905 bare = self._bare,
1906 capture_stdout = True,
1907 capture_stderr = True)
1908 r = []
1909 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001910 if line[-1] == '\n':
1911 line = line[:-1]
1912 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001913 if p.Wait() != 0:
1914 raise GitError('%s rev-list %s: %s' % (
1915 self._project.name,
1916 str(args),
1917 p.stderr))
1918 return r
1919
1920 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001921 """Allow arbitrary git commands using pythonic syntax.
1922
1923 This allows you to do things like:
1924 git_obj.rev_parse('HEAD')
1925
1926 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1927 run. We'll replace the '_' with a '-' and try to run a git command.
1928 Any other arguments will be passed to the git command.
1929
1930 Args:
1931 name: The name of the git command to call. Any '_' characters will
1932 be replaced with '-'.
1933
1934 Returns:
1935 A callable object that will try to call git with the named command.
1936 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001937 name = name.replace('_', '-')
1938 def runner(*args):
1939 cmdv = [name]
1940 cmdv.extend(args)
1941 p = GitCommand(self._project,
1942 cmdv,
1943 bare = self._bare,
1944 capture_stdout = True,
1945 capture_stderr = True)
1946 if p.Wait() != 0:
1947 raise GitError('%s %s: %s' % (
1948 self._project.name,
1949 name,
1950 p.stderr))
1951 r = p.stdout
1952 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1953 return r[:-1]
1954 return r
1955 return runner
1956
1957
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001958class _PriorSyncFailedError(Exception):
1959 def __str__(self):
1960 return 'prior sync failed; rebase still in progress'
1961
1962class _DirtyError(Exception):
1963 def __str__(self):
1964 return 'contains uncommitted changes'
1965
1966class _InfoMessage(object):
1967 def __init__(self, project, text):
1968 self.project = project
1969 self.text = text
1970
1971 def Print(self, syncbuf):
1972 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1973 syncbuf.out.nl()
1974
1975class _Failure(object):
1976 def __init__(self, project, why):
1977 self.project = project
1978 self.why = why
1979
1980 def Print(self, syncbuf):
1981 syncbuf.out.fail('error: %s/: %s',
1982 self.project.relpath,
1983 str(self.why))
1984 syncbuf.out.nl()
1985
1986class _Later(object):
1987 def __init__(self, project, action):
1988 self.project = project
1989 self.action = action
1990
1991 def Run(self, syncbuf):
1992 out = syncbuf.out
1993 out.project('project %s/', self.project.relpath)
1994 out.nl()
1995 try:
1996 self.action()
1997 out.nl()
1998 return True
1999 except GitError, e:
2000 out.nl()
2001 return False
2002
2003class _SyncColoring(Coloring):
2004 def __init__(self, config):
2005 Coloring.__init__(self, config, 'reposync')
2006 self.project = self.printer('header', attr = 'bold')
2007 self.info = self.printer('info')
2008 self.fail = self.printer('fail', fg='red')
2009
2010class SyncBuffer(object):
2011 def __init__(self, config, detach_head=False):
2012 self._messages = []
2013 self._failures = []
2014 self._later_queue1 = []
2015 self._later_queue2 = []
2016
2017 self.out = _SyncColoring(config)
2018 self.out.redirect(sys.stderr)
2019
2020 self.detach_head = detach_head
2021 self.clean = True
2022
2023 def info(self, project, fmt, *args):
2024 self._messages.append(_InfoMessage(project, fmt % args))
2025
2026 def fail(self, project, err=None):
2027 self._failures.append(_Failure(project, err))
2028 self.clean = False
2029
2030 def later1(self, project, what):
2031 self._later_queue1.append(_Later(project, what))
2032
2033 def later2(self, project, what):
2034 self._later_queue2.append(_Later(project, what))
2035
2036 def Finish(self):
2037 self._PrintMessages()
2038 self._RunLater()
2039 self._PrintMessages()
2040 return self.clean
2041
2042 def _RunLater(self):
2043 for q in ['_later_queue1', '_later_queue2']:
2044 if not self._RunQueue(q):
2045 return
2046
2047 def _RunQueue(self, queue):
2048 for m in getattr(self, queue):
2049 if not m.Run(self):
2050 self.clean = False
2051 return False
2052 setattr(self, queue, [])
2053 return True
2054
2055 def _PrintMessages(self):
2056 for m in self._messages:
2057 m.Print(self)
2058 for m in self._failures:
2059 m.Print(self)
2060
2061 self._messages = []
2062 self._failures = []
2063
2064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002065class MetaProject(Project):
2066 """A special project housed under .repo.
2067 """
2068 def __init__(self, manifest, name, gitdir, worktree):
2069 repodir = manifest.repodir
2070 Project.__init__(self,
2071 manifest = manifest,
2072 name = name,
2073 gitdir = gitdir,
2074 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002075 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002076 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002077 revisionExpr = 'refs/heads/master',
2078 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002079
2080 def PreSync(self):
2081 if self.Exists:
2082 cb = self.CurrentBranch
2083 if cb:
2084 base = self.GetBranch(cb).merge
2085 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002086 self.revisionExpr = base
2087 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002088
2089 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002090 def LastFetch(self):
2091 try:
2092 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2093 return os.path.getmtime(fh)
2094 except OSError:
2095 return 0
2096
2097 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098 def HasChanges(self):
2099 """Has the remote received new commits not yet checked out?
2100 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002101 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002102 return False
2103
2104 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002105 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002106 head = self.work_git.GetHead()
2107 if head.startswith(R_HEADS):
2108 try:
2109 head = all[head]
2110 except KeyError:
2111 head = None
2112
2113 if revid == head:
2114 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002115 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002116 return True
2117 return False