blob: b61bcacbc3dc14bb27ff94960fef01b5e998e63c [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
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070039from git_config import GitConfig, IsId, GetSchemeFromUrl
40from 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. Pearce370e3fa2009-01-26 10:55:39 -0800869 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800870 if dest_branch.startswith(R_HEADS):
871 dest_branch = dest_branch[len(R_HEADS):]
872
873 rp = ['gerrit receive-pack']
874 for e in people[0]:
875 rp.append('--reviewer=%s' % sq(e))
876 for e in people[1]:
877 rp.append('--cc=%s' % sq(e))
878
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700879 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
880 if auto_topic:
881 ref_spec = ref_spec + '/' + branch.name
882
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800883 cmd = ['push']
884 cmd.append('--receive-pack=%s' % " ".join(rp))
885 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700886 cmd.append(ref_spec)
887
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800888 if GitCommand(self, cmd, bare = True).Wait() != 0:
889 raise UploadError('Upload failed')
890
891 else:
892 raise UploadError('Unsupported protocol %s' \
893 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894
895 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
896 self.bare_git.UpdateRef(R_PUB + branch.name,
897 R_HEADS + branch.name,
898 message = msg)
899
900
901## Sync ##
902
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700903 def Sync_NetworkHalf(self, quiet=False, is_new=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 """Perform only the network IO portion of the sync process.
905 Local working directory/branch state is not affected.
906 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700907 if is_new is None:
908 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200909 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 self._InitGitDir()
911 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700912
913 if is_new:
914 alt = os.path.join(self.gitdir, 'objects/info/alternates')
915 try:
916 fd = open(alt, 'rb')
917 try:
918 alt_dir = fd.readline().rstrip()
919 finally:
920 fd.close()
921 except IOError:
922 alt_dir = None
923 else:
924 alt_dir = None
925
926 if alt_dir is None and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
927 is_new = False
928
929 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800931
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200932 #Check that the requested ref was found after fetch
933 #
934 try:
935 self.GetRevisionId()
936 except ManifestInvalidRevisionError:
937 # if the ref is a tag. We can try fetching
938 # the tag manually as a last resort
939 #
940 rev = self.revisionExpr
941 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700942 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200943
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800944 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800945 self._InitMRef()
946 else:
947 self._InitMirrorHead()
948 try:
949 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
950 except OSError:
951 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800953
954 def PostRepoUpgrade(self):
955 self._InitHooks()
956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 def _CopyFiles(self):
958 for file in self.copyfiles:
959 file._Copy()
960
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700961 def GetRevisionId(self, all=None):
962 if self.revisionId:
963 return self.revisionId
964
965 rem = self.GetRemote(self.remote.name)
966 rev = rem.ToLocal(self.revisionExpr)
967
968 if all is not None and rev in all:
969 return all[rev]
970
971 try:
972 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
973 except GitError:
974 raise ManifestInvalidRevisionError(
975 'revision %s in %s not found' % (self.revisionExpr,
976 self.name))
977
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700978 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 """Perform only the local IO portion of the sync process.
980 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700982 all = self.bare_ref.all
983 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700984 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -0800985
986 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700987 head = self.work_git.GetHead()
988 if head.startswith(R_HEADS):
989 branch = head[len(R_HEADS):]
990 try:
991 head = all[head]
992 except KeyError:
993 head = None
994 else:
995 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700997 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 # Currently on a detached HEAD. The user is assumed to
999 # not have any local modifications worth worrying about.
1000 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001001 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001002 syncbuf.fail(self, _PriorSyncFailedError())
1003 return
1004
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001005 if head == revid:
1006 # No changes; don't do anything further.
1007 #
1008 return
1009
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001010 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001012 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001014 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001015 except GitError, e:
1016 syncbuf.fail(self, e)
1017 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001019 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001021 if head == revid:
1022 # No changes; don't do anything further.
1023 #
1024 return
1025
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001028 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 # The current branch has no tracking configuration.
1030 # Jump off it to a deatched HEAD.
1031 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 syncbuf.info(self,
1033 "leaving %s; does not track upstream",
1034 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001036 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001037 except GitError, e:
1038 syncbuf.fail(self, e)
1039 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001041 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001043 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001044 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001046 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047 if not_merged:
1048 if upstream_gain:
1049 # The user has published this branch and some of those
1050 # commits are not yet merged upstream. We do not want
1051 # to rewrite the published commits so we punt.
1052 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001053 syncbuf.fail(self,
1054 "branch %s is published (but not merged) and is now %d commits behind"
1055 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001057 elif pub == head:
1058 # All published commits are merged, and thus we are a
1059 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001060 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001061 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001062 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001063 self._CopyFiles()
1064 syncbuf.later1(self, _doff)
1065 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001067 # Examine the local commits not in the remote. Find the
1068 # last one attributed to this user, if any.
1069 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001070 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001071 last_mine = None
1072 cnt_mine = 0
1073 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001074 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001075 if committer_email == self.UserEmail:
1076 last_mine = commit_id
1077 cnt_mine += 1
1078
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001079 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001083 syncbuf.fail(self, _DirtyError())
1084 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 # If the upstream switched on us, warn the user.
1087 #
1088 if branch.merge != self.revisionExpr:
1089 if branch.merge and self.revisionExpr:
1090 syncbuf.info(self,
1091 'manifest switched %s...%s',
1092 branch.merge,
1093 self.revisionExpr)
1094 elif branch.merge:
1095 syncbuf.info(self,
1096 'manifest no longer tracks %s',
1097 branch.merge)
1098
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001101 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 syncbuf.info(self,
1104 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001107 branch.remote = self.GetRemote(self.remote.name)
1108 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109 branch.Save()
1110
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001111 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001112 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 self._CopyFiles()
1115 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001116 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001118 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001119 self._CopyFiles()
1120 except GitError, e:
1121 syncbuf.fail(self, e)
1122 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001124 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001125 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 self._CopyFiles()
1127 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001129 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130 # dest should already be an absolute path, but src is project relative
1131 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001132 abssrc = os.path.join(self.worktree, src)
1133 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001135 def DownloadPatchSet(self, change_id, patch_id):
1136 """Download a single patch set of a single change to FETCH_HEAD.
1137 """
1138 remote = self.GetRemote(self.remote.name)
1139
1140 cmd = ['fetch', remote.name]
1141 cmd.append('refs/changes/%2.2d/%d/%d' \
1142 % (change_id % 100, change_id, patch_id))
1143 cmd.extend(map(lambda x: str(x), remote.fetch))
1144 if GitCommand(self, cmd, bare=True).Wait() != 0:
1145 return None
1146 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001147 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001148 change_id,
1149 patch_id,
1150 self.bare_git.rev_parse('FETCH_HEAD'))
1151
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152
1153## Branch Management ##
1154
1155 def StartBranch(self, name):
1156 """Create a new branch off the manifest's revision.
1157 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001158 head = self.work_git.GetHead()
1159 if head == (R_HEADS + name):
1160 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001161
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001162 all = self.bare_ref.all
1163 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001164 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001165 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001166 capture_stdout = True,
1167 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001168
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001169 branch = self.GetBranch(name)
1170 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001171 branch.merge = self.revisionExpr
1172 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001173
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001174 if head.startswith(R_HEADS):
1175 try:
1176 head = all[head]
1177 except KeyError:
1178 head = None
1179
1180 if revid and head and revid == head:
1181 ref = os.path.join(self.gitdir, R_HEADS + name)
1182 try:
1183 os.makedirs(os.path.dirname(ref))
1184 except OSError:
1185 pass
1186 _lwrite(ref, '%s\n' % revid)
1187 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1188 'ref: %s%s\n' % (R_HEADS, name))
1189 branch.Save()
1190 return True
1191
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001192 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001193 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001194 capture_stdout = True,
1195 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001196 branch.Save()
1197 return True
1198 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199
Wink Saville02d79452009-04-10 13:01:24 -07001200 def CheckoutBranch(self, name):
1201 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001202
1203 Args:
1204 name: The name of the branch to checkout.
1205
1206 Returns:
1207 True if the checkout succeeded; False if it didn't; None if the branch
1208 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001209 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001210 rev = R_HEADS + name
1211 head = self.work_git.GetHead()
1212 if head == rev:
1213 # Already on the branch
1214 #
1215 return True
Wink Saville02d79452009-04-10 13:01:24 -07001216
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001217 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001218 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001219 revid = all[rev]
1220 except KeyError:
1221 # Branch does not exist in this project
1222 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001223 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001224
1225 if head.startswith(R_HEADS):
1226 try:
1227 head = all[head]
1228 except KeyError:
1229 head = None
1230
1231 if head == revid:
1232 # Same revision; just update HEAD to point to the new
1233 # target branch, but otherwise take no other action.
1234 #
1235 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1236 'ref: %s%s\n' % (R_HEADS, name))
1237 return True
Wink Saville02d79452009-04-10 13:01:24 -07001238
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001239 return GitCommand(self,
1240 ['checkout', name, '--'],
1241 capture_stdout = True,
1242 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001243
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001244 def AbandonBranch(self, name):
1245 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001246
1247 Args:
1248 name: The name of the branch to abandon.
1249
1250 Returns:
1251 True if the abandon succeeded; False if it didn't; None if the branch
1252 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001253 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001254 rev = R_HEADS + name
1255 all = self.bare_ref.all
1256 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001257 # Doesn't exist
1258 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001259
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001260 head = self.work_git.GetHead()
1261 if head == rev:
1262 # We can't destroy the branch while we are sitting
1263 # on it. Switch to a detached HEAD.
1264 #
1265 head = all[head]
1266
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001267 revid = self.GetRevisionId(all)
1268 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001269 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1270 '%s\n' % revid)
1271 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001272 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001273
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001274 return GitCommand(self,
1275 ['branch', '-D', name],
1276 capture_stdout = True,
1277 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001278
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 def PruneHeads(self):
1280 """Prune any topic branches already merged into upstream.
1281 """
1282 cb = self.CurrentBranch
1283 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001284 left = self._allrefs
1285 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 if name.startswith(R_HEADS):
1287 name = name[len(R_HEADS):]
1288 if cb is None or name != cb:
1289 kill.append(name)
1290
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001291 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 if cb is not None \
1293 and not self._revlist(HEAD + '...' + rev) \
1294 and not self.IsDirty(consider_untracked = False):
1295 self.work_git.DetachHead(HEAD)
1296 kill.append(cb)
1297
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001299 old = self.bare_git.GetHead()
1300 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1302
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 try:
1304 self.bare_git.DetachHead(rev)
1305
1306 b = ['branch', '-d']
1307 b.extend(kill)
1308 b = GitCommand(self, b, bare=True,
1309 capture_stdout=True,
1310 capture_stderr=True)
1311 b.Wait()
1312 finally:
1313 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001314 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001316 for branch in kill:
1317 if (R_HEADS + branch) not in left:
1318 self.CleanPublishedCache()
1319 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320
1321 if cb and cb not in kill:
1322 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001323 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
1325 kept = []
1326 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001327 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 branch = self.GetBranch(branch)
1329 base = branch.LocalMerge
1330 if not base:
1331 base = rev
1332 kept.append(ReviewableBranch(self, branch, base))
1333 return kept
1334
1335
1336## Direct Git Commands ##
1337
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001338 def _RemoteFetch(self, name=None, tag=None,
1339 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001340 quiet=False,
1341 alt_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 if not name:
1343 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001344
1345 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001346 remote = self.GetRemote(name)
1347 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001348 ssh_proxy = True
1349
Shawn O. Pearce88443382010-10-08 10:02:09 +02001350 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001351 if alt_dir and 'objects' == os.path.basename(alt_dir):
1352 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001353 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1354 remote = self.GetRemote(name)
1355
1356 all = self.bare_ref.all
1357 ids = set(all.values())
1358 tmp = set()
1359
1360 for r, id in GitRefs(ref_dir).all.iteritems():
1361 if r not in all:
1362 if r.startswith(R_TAGS) or remote.WritesTo(r):
1363 all[r] = id
1364 ids.add(id)
1365 continue
1366
1367 if id in ids:
1368 continue
1369
1370 r = 'refs/_alt/%s' % id
1371 all[r] = id
1372 ids.add(id)
1373 tmp.add(r)
1374
1375 ref_names = list(all.keys())
1376 ref_names.sort()
1377
1378 tmp_packed = ''
1379 old_packed = ''
1380
1381 for r in ref_names:
1382 line = '%s %s\n' % (all[r], r)
1383 tmp_packed += line
1384 if r not in tmp:
1385 old_packed += line
1386
1387 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001388 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001389 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001390
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001391 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001392
1393 # The --depth option only affects the initial fetch; after that we'll do
1394 # full fetches of changes.
1395 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1396 if depth and initial:
1397 cmd.append('--depth=%s' % depth)
1398
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001399 if quiet:
1400 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001401 if not self.worktree:
1402 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001403 cmd.append(name)
1404 if tag is not None:
1405 cmd.append('tag')
1406 cmd.append(tag)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001407
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001408 ok = False
1409 for i in range(2):
1410 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1411 ok = True
1412 break
1413 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001414
1415 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001416 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001417 if old_packed != '':
1418 _lwrite(packed_refs, old_packed)
1419 else:
1420 os.remove(packed_refs)
1421 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001422 return ok
1423
1424 def _ApplyCloneBundle(self, initial=False, quiet=False):
1425 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1426 return False
1427
1428 remote = self.GetRemote(self.remote.name)
1429 bundle_url = remote.url + '/clone.bundle'
1430 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1431 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1432 return False
1433
1434 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1435 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001436
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001437 exist_dst = os.path.exists(bundle_dst)
1438 exist_tmp = os.path.exists(bundle_tmp)
1439
1440 if not initial and not exist_dst and not exist_tmp:
1441 return False
1442
1443 if not exist_dst:
1444 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1445 if not exist_dst:
1446 return False
1447
1448 cmd = ['fetch']
1449 if quiet:
1450 cmd.append('--quiet')
1451 if not self.worktree:
1452 cmd.append('--update-head-ok')
1453 cmd.append(bundle_dst)
1454 for f in remote.fetch:
1455 cmd.append(str(f))
1456 cmd.append('refs/tags/*:refs/tags/*')
1457
1458 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001459 if os.path.exists(bundle_dst):
1460 os.remove(bundle_dst)
1461 if os.path.exists(bundle_tmp):
1462 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001463 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001464
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001465 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001466 keep = True
1467 done = False
1468 dest = open(tmpPath, 'a+b')
1469 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001470 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001471 pos = dest.tell()
1472
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001473 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001474 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001475 req = urllib2.Request(srcUrl)
1476 if pos > 0:
1477 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001478
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001479 try:
1480 r = urllib2.urlopen(req)
1481 except urllib2.HTTPError, e:
1482 def _content_type():
1483 try:
1484 return e.info()['content-type']
1485 except:
1486 return None
1487
1488 if e.code == 404:
1489 keep = False
1490 return False
1491 elif _content_type() == 'text/plain':
1492 try:
1493 msg = e.read()
1494 if len(msg) > 0 and msg[-1] == '\n':
1495 msg = msg[0:-1]
1496 msg = ' (%s)' % msg
1497 except:
1498 msg = ''
1499 else:
1500 try:
1501 from BaseHTTPServer import BaseHTTPRequestHandler
1502 res = BaseHTTPRequestHandler.responses[e.code]
1503 msg = ' (%s: %s)' % (res[0], res[1])
1504 except:
1505 msg = ''
1506 raise DownloadError('HTTP %s%s' % (e.code, msg))
1507 except urllib2.URLError, e:
1508 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1509 finally:
1510 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001511
1512 p = None
1513 try:
1514 size = r.headers['content-length']
1515 unit = 1 << 10
1516
1517 if size and not quiet:
1518 if size > 1024 * 1.3:
1519 unit = 1 << 20
1520 desc = 'MB'
1521 else:
1522 desc = 'KB'
1523 p = Progress(
1524 'Downloading %s' % self.relpath,
1525 int(size) / unit,
1526 units=desc)
1527 if pos > 0:
1528 p.update(pos / unit)
1529
1530 s = 0
1531 while True:
1532 d = r.read(8192)
1533 if d == '':
1534 done = True
1535 return True
1536 dest.write(d)
1537 if p:
1538 s += len(d)
1539 if s >= unit:
1540 p.update(s / unit)
1541 s = s % unit
1542 if p:
1543 if s >= unit:
1544 p.update(s / unit)
1545 else:
1546 p.update(1)
1547 finally:
1548 r.close()
1549 if p:
1550 p.end()
1551 finally:
1552 dest.close()
1553
1554 if os.path.exists(dstPath):
1555 os.remove(dstPath)
1556 if done:
1557 os.rename(tmpPath, dstPath)
1558 elif not keep:
1559 os.remove(tmpPath)
1560
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561 def _Checkout(self, rev, quiet=False):
1562 cmd = ['checkout']
1563 if quiet:
1564 cmd.append('-q')
1565 cmd.append(rev)
1566 cmd.append('--')
1567 if GitCommand(self, cmd).Wait() != 0:
1568 if self._allrefs:
1569 raise GitError('%s checkout %s ' % (self.name, rev))
1570
1571 def _ResetHard(self, rev, quiet=True):
1572 cmd = ['reset', '--hard']
1573 if quiet:
1574 cmd.append('-q')
1575 cmd.append(rev)
1576 if GitCommand(self, cmd).Wait() != 0:
1577 raise GitError('%s reset --hard %s ' % (self.name, rev))
1578
1579 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001580 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001581 if onto is not None:
1582 cmd.extend(['--onto', onto])
1583 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001584 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585 raise GitError('%s rebase %s ' % (self.name, upstream))
1586
1587 def _FastForward(self, head):
1588 cmd = ['merge', head]
1589 if GitCommand(self, cmd).Wait() != 0:
1590 raise GitError('%s merge %s ' % (self.name, head))
1591
1592 def _InitGitDir(self):
1593 if not os.path.exists(self.gitdir):
1594 os.makedirs(self.gitdir)
1595 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001596
Shawn O. Pearce88443382010-10-08 10:02:09 +02001597 mp = self.manifest.manifestProject
1598 ref_dir = mp.config.GetString('repo.reference')
1599
1600 if ref_dir:
1601 mirror_git = os.path.join(ref_dir, self.name + '.git')
1602 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1603 self.relpath + '.git')
1604
1605 if os.path.exists(mirror_git):
1606 ref_dir = mirror_git
1607
1608 elif os.path.exists(repo_git):
1609 ref_dir = repo_git
1610
1611 else:
1612 ref_dir = None
1613
1614 if ref_dir:
1615 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1616 os.path.join(ref_dir, 'objects') + '\n')
1617
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001618 if self.manifest.IsMirror:
1619 self.config.SetString('core.bare', 'true')
1620 else:
1621 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001622
1623 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001624 try:
1625 to_rm = os.listdir(hooks)
1626 except OSError:
1627 to_rm = []
1628 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001629 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001630 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001631
1632 m = self.manifest.manifestProject.config
1633 for key in ['user.name', 'user.email']:
1634 if m.Has(key, include_defaults = False):
1635 self.config.SetString(key, m.GetString(key))
1636
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001637 def _InitHooks(self):
1638 hooks = self._gitdir_path('hooks')
1639 if not os.path.exists(hooks):
1640 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001641 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001642 name = os.path.basename(stock_hook)
1643
Victor Boivie65e0f352011-04-18 11:23:29 +02001644 if name in ('commit-msg',) and not self.remote.review \
1645 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001646 # Don't install a Gerrit Code Review hook if this
1647 # project does not appear to use it for reviews.
1648 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001649 # Since the manifest project is one of those, but also
1650 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001651 continue
1652
1653 dst = os.path.join(hooks, name)
1654 if os.path.islink(dst):
1655 continue
1656 if os.path.exists(dst):
1657 if filecmp.cmp(stock_hook, dst, shallow=False):
1658 os.remove(dst)
1659 else:
1660 _error("%s: Not replacing %s hook", self.relpath, name)
1661 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001662 try:
1663 os.symlink(relpath(stock_hook, dst), dst)
1664 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001665 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001666 raise GitError('filesystem must support symlinks')
1667 else:
1668 raise
1669
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001671 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001672 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001673 remote.url = self.remote.url
1674 remote.review = self.remote.review
1675 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001677 if self.worktree:
1678 remote.ResetFetch(mirror=False)
1679 else:
1680 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681 remote.Save()
1682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 def _InitMRef(self):
1684 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001685 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001687 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001688 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001689
1690 def _InitAnyMRef(self, ref):
1691 cur = self.bare_ref.symref(ref)
1692
1693 if self.revisionId:
1694 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1695 msg = 'manifest set to %s' % self.revisionId
1696 dst = self.revisionId + '^0'
1697 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1698 else:
1699 remote = self.GetRemote(self.remote.name)
1700 dst = remote.ToLocal(self.revisionExpr)
1701 if cur != dst:
1702 msg = 'manifest set to %s' % self.revisionExpr
1703 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 def _InitWorkTree(self):
1706 dotgit = os.path.join(self.worktree, '.git')
1707 if not os.path.exists(dotgit):
1708 os.makedirs(dotgit)
1709
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710 for name in ['config',
1711 'description',
1712 'hooks',
1713 'info',
1714 'logs',
1715 'objects',
1716 'packed-refs',
1717 'refs',
1718 'rr-cache',
1719 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001720 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001721 src = os.path.join(self.gitdir, name)
1722 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001723 if os.path.islink(dst) or not os.path.exists(dst):
1724 os.symlink(relpath(src, dst), dst)
1725 else:
1726 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001727 except OSError, e:
1728 if e.errno == errno.EPERM:
1729 raise GitError('filesystem must support symlinks')
1730 else:
1731 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001733 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734
1735 cmd = ['read-tree', '--reset', '-u']
1736 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001737 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 if GitCommand(self, cmd).Wait() != 0:
1739 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001740
1741 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1742 if not os.path.exists(rr_cache):
1743 os.makedirs(rr_cache)
1744
Shawn O. Pearce93609662009-04-21 10:50:33 -07001745 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746
1747 def _gitdir_path(self, path):
1748 return os.path.join(self.gitdir, path)
1749
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001750 def _revlist(self, *args, **kw):
1751 a = []
1752 a.extend(args)
1753 a.append('--')
1754 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755
1756 @property
1757 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001758 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001759
1760 class _GitGetByExec(object):
1761 def __init__(self, project, bare):
1762 self._project = project
1763 self._bare = bare
1764
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765 def LsOthers(self):
1766 p = GitCommand(self._project,
1767 ['ls-files',
1768 '-z',
1769 '--others',
1770 '--exclude-standard'],
1771 bare = False,
1772 capture_stdout = True,
1773 capture_stderr = True)
1774 if p.Wait() == 0:
1775 out = p.stdout
1776 if out:
1777 return out[:-1].split("\0")
1778 return []
1779
1780 def DiffZ(self, name, *args):
1781 cmd = [name]
1782 cmd.append('-z')
1783 cmd.extend(args)
1784 p = GitCommand(self._project,
1785 cmd,
1786 bare = False,
1787 capture_stdout = True,
1788 capture_stderr = True)
1789 try:
1790 out = p.process.stdout.read()
1791 r = {}
1792 if out:
1793 out = iter(out[:-1].split('\0'))
1794 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001795 try:
1796 info = out.next()
1797 path = out.next()
1798 except StopIteration:
1799 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800
1801 class _Info(object):
1802 def __init__(self, path, omode, nmode, oid, nid, state):
1803 self.path = path
1804 self.src_path = None
1805 self.old_mode = omode
1806 self.new_mode = nmode
1807 self.old_id = oid
1808 self.new_id = nid
1809
1810 if len(state) == 1:
1811 self.status = state
1812 self.level = None
1813 else:
1814 self.status = state[:1]
1815 self.level = state[1:]
1816 while self.level.startswith('0'):
1817 self.level = self.level[1:]
1818
1819 info = info[1:].split(' ')
1820 info =_Info(path, *info)
1821 if info.status in ('R', 'C'):
1822 info.src_path = info.path
1823 info.path = out.next()
1824 r[info.path] = info
1825 return r
1826 finally:
1827 p.Wait()
1828
1829 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001830 if self._bare:
1831 path = os.path.join(self._project.gitdir, HEAD)
1832 else:
1833 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001834 fd = open(path, 'rb')
1835 try:
1836 line = fd.read()
1837 finally:
1838 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001839 if line.startswith('ref: '):
1840 return line[5:-1]
1841 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842
1843 def SetHead(self, ref, message=None):
1844 cmdv = []
1845 if message is not None:
1846 cmdv.extend(['-m', message])
1847 cmdv.append(HEAD)
1848 cmdv.append(ref)
1849 self.symbolic_ref(*cmdv)
1850
1851 def DetachHead(self, new, message=None):
1852 cmdv = ['--no-deref']
1853 if message is not None:
1854 cmdv.extend(['-m', message])
1855 cmdv.append(HEAD)
1856 cmdv.append(new)
1857 self.update_ref(*cmdv)
1858
1859 def UpdateRef(self, name, new, old=None,
1860 message=None,
1861 detach=False):
1862 cmdv = []
1863 if message is not None:
1864 cmdv.extend(['-m', message])
1865 if detach:
1866 cmdv.append('--no-deref')
1867 cmdv.append(name)
1868 cmdv.append(new)
1869 if old is not None:
1870 cmdv.append(old)
1871 self.update_ref(*cmdv)
1872
1873 def DeleteRef(self, name, old=None):
1874 if not old:
1875 old = self.rev_parse(name)
1876 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001877 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001878
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001879 def rev_list(self, *args, **kw):
1880 if 'format' in kw:
1881 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1882 else:
1883 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001884 cmdv.extend(args)
1885 p = GitCommand(self._project,
1886 cmdv,
1887 bare = self._bare,
1888 capture_stdout = True,
1889 capture_stderr = True)
1890 r = []
1891 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001892 if line[-1] == '\n':
1893 line = line[:-1]
1894 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001895 if p.Wait() != 0:
1896 raise GitError('%s rev-list %s: %s' % (
1897 self._project.name,
1898 str(args),
1899 p.stderr))
1900 return r
1901
1902 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001903 """Allow arbitrary git commands using pythonic syntax.
1904
1905 This allows you to do things like:
1906 git_obj.rev_parse('HEAD')
1907
1908 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1909 run. We'll replace the '_' with a '-' and try to run a git command.
1910 Any other arguments will be passed to the git command.
1911
1912 Args:
1913 name: The name of the git command to call. Any '_' characters will
1914 be replaced with '-'.
1915
1916 Returns:
1917 A callable object that will try to call git with the named command.
1918 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001919 name = name.replace('_', '-')
1920 def runner(*args):
1921 cmdv = [name]
1922 cmdv.extend(args)
1923 p = GitCommand(self._project,
1924 cmdv,
1925 bare = self._bare,
1926 capture_stdout = True,
1927 capture_stderr = True)
1928 if p.Wait() != 0:
1929 raise GitError('%s %s: %s' % (
1930 self._project.name,
1931 name,
1932 p.stderr))
1933 r = p.stdout
1934 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1935 return r[:-1]
1936 return r
1937 return runner
1938
1939
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001940class _PriorSyncFailedError(Exception):
1941 def __str__(self):
1942 return 'prior sync failed; rebase still in progress'
1943
1944class _DirtyError(Exception):
1945 def __str__(self):
1946 return 'contains uncommitted changes'
1947
1948class _InfoMessage(object):
1949 def __init__(self, project, text):
1950 self.project = project
1951 self.text = text
1952
1953 def Print(self, syncbuf):
1954 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1955 syncbuf.out.nl()
1956
1957class _Failure(object):
1958 def __init__(self, project, why):
1959 self.project = project
1960 self.why = why
1961
1962 def Print(self, syncbuf):
1963 syncbuf.out.fail('error: %s/: %s',
1964 self.project.relpath,
1965 str(self.why))
1966 syncbuf.out.nl()
1967
1968class _Later(object):
1969 def __init__(self, project, action):
1970 self.project = project
1971 self.action = action
1972
1973 def Run(self, syncbuf):
1974 out = syncbuf.out
1975 out.project('project %s/', self.project.relpath)
1976 out.nl()
1977 try:
1978 self.action()
1979 out.nl()
1980 return True
1981 except GitError, e:
1982 out.nl()
1983 return False
1984
1985class _SyncColoring(Coloring):
1986 def __init__(self, config):
1987 Coloring.__init__(self, config, 'reposync')
1988 self.project = self.printer('header', attr = 'bold')
1989 self.info = self.printer('info')
1990 self.fail = self.printer('fail', fg='red')
1991
1992class SyncBuffer(object):
1993 def __init__(self, config, detach_head=False):
1994 self._messages = []
1995 self._failures = []
1996 self._later_queue1 = []
1997 self._later_queue2 = []
1998
1999 self.out = _SyncColoring(config)
2000 self.out.redirect(sys.stderr)
2001
2002 self.detach_head = detach_head
2003 self.clean = True
2004
2005 def info(self, project, fmt, *args):
2006 self._messages.append(_InfoMessage(project, fmt % args))
2007
2008 def fail(self, project, err=None):
2009 self._failures.append(_Failure(project, err))
2010 self.clean = False
2011
2012 def later1(self, project, what):
2013 self._later_queue1.append(_Later(project, what))
2014
2015 def later2(self, project, what):
2016 self._later_queue2.append(_Later(project, what))
2017
2018 def Finish(self):
2019 self._PrintMessages()
2020 self._RunLater()
2021 self._PrintMessages()
2022 return self.clean
2023
2024 def _RunLater(self):
2025 for q in ['_later_queue1', '_later_queue2']:
2026 if not self._RunQueue(q):
2027 return
2028
2029 def _RunQueue(self, queue):
2030 for m in getattr(self, queue):
2031 if not m.Run(self):
2032 self.clean = False
2033 return False
2034 setattr(self, queue, [])
2035 return True
2036
2037 def _PrintMessages(self):
2038 for m in self._messages:
2039 m.Print(self)
2040 for m in self._failures:
2041 m.Print(self)
2042
2043 self._messages = []
2044 self._failures = []
2045
2046
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002047class MetaProject(Project):
2048 """A special project housed under .repo.
2049 """
2050 def __init__(self, manifest, name, gitdir, worktree):
2051 repodir = manifest.repodir
2052 Project.__init__(self,
2053 manifest = manifest,
2054 name = name,
2055 gitdir = gitdir,
2056 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002057 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002059 revisionExpr = 'refs/heads/master',
2060 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002061
2062 def PreSync(self):
2063 if self.Exists:
2064 cb = self.CurrentBranch
2065 if cb:
2066 base = self.GetBranch(cb).merge
2067 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002068 self.revisionExpr = base
2069 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002070
2071 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002072 def LastFetch(self):
2073 try:
2074 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2075 return os.path.getmtime(fh)
2076 except OSError:
2077 return 0
2078
2079 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002080 def HasChanges(self):
2081 """Has the remote received new commits not yet checked out?
2082 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002083 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002084 return False
2085
2086 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002087 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002088 head = self.work_git.GetHead()
2089 if head.startswith(R_HEADS):
2090 try:
2091 head = all[head]
2092 except KeyError:
2093 head = None
2094
2095 if revid == head:
2096 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002097 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098 return True
2099 return False