blob: b3e1a4776d0b99e7afac2ba70df71e0854fdc9e1 [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
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070034 ID_RE, RefSpec
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070036from error import CacheApplyError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080037from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080038from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070039from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Shawn O. Pearced237b692009-04-17 18:49:50 -070041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
David Pursehouse59bbb582013-05-17 10:49:33 +090043from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040044if is_python3():
45 import urllib.parse
46else:
47 import imp
48 import urlparse
49 urllib = imp.new_module('urllib')
50 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090051 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053052 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090053 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070055
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056def _lwrite(path, content):
57 lock = '%s.lock' % path
58
Chirayu Desai303a82f2014-08-19 22:57:17 +053059 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 try:
61 fd.write(content)
62 finally:
63 fd.close()
64
65 try:
66 os.rename(lock, path)
67 except OSError:
68 os.remove(lock)
69 raise
70
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
Shawn O. Pearce48244782009-04-16 08:25:57 -070072def _error(fmt, *args):
73 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070074 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070075
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
David Pursehousef33929d2015-08-24 14:39:14 +090077def _warn(fmt, *args):
78 msg = fmt % args
79 print('warn: %s' % msg, file=sys.stderr)
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082def not_rev(r):
83 return '^' + r
84
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070085
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080086def sq(r):
87 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
106 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
107 d = os.path.join(d, 'hooks')
108 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
138
139 def __init__(self, project, branch, base):
140 self.project = project
141 self.branch = branch
142 self.base = base
143
144 @property
145 def name(self):
146 return self.branch.name
147
148 @property
149 def commits(self):
150 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700151 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700164 for commit in self.project.bare_git.rev_list(not_rev(self.base),
165 R_HEADS + self.name,
166 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 r[commit[0:8]] = commit
168 return r
169
170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 return self.project.bare_git.log('--pretty=format:%cd',
173 '-n', '1',
174 R_HEADS + self.name,
175 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700177 def UploadForReview(self, people,
178 auto_topic=False,
179 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200180 private=False,
181 wip=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700182 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800183 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700184 people,
Brian Harring435370c2012-07-28 15:37:04 -0700185 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400186 draft=draft,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200187 private=private,
188 wip=wip,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400189 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700191 def GetPublishedRefs(self):
192 refs = {}
193 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700194 self.branch.remote.SshReviewUrl(self.project.UserEmail),
195 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700196 for line in output.split('\n'):
197 try:
198 (sha, ref) = line.split()
199 refs[sha] = ref
200 except ValueError:
201 pass
202
203 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700205
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 def __init__(self, config):
209 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100210 self.project = self.printer('header', attr='bold')
211 self.branch = self.printer('header', attr='bold')
212 self.nobranch = self.printer('nobranch', fg='red')
213 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
Anthony King7bdac712014-07-16 12:56:40 +0100215 self.added = self.printer('added', fg='green')
216 self.changed = self.printer('changed', fg='red')
217 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218
219
220class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 def __init__(self, config):
223 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100224 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700226
Anthony King7bdac712014-07-16 12:56:40 +0100227class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228
James W. Mills24c13082012-04-12 15:04:13 -0500229 def __init__(self, name, value, keep):
230 self.name = name
231 self.value = value
232 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
Anthony King7bdac712014-07-16 12:56:40 +0100235class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800237 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238 self.src = src
239 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800240 self.abs_src = abssrc
241 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242
243 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800244 src = self.abs_src
245 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246 # copy file if it does not exist or is out of date
247 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
248 try:
249 # remove existing file first, since it might be read-only
250 if os.path.exists(dest):
251 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400252 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200253 dest_dir = os.path.dirname(dest)
254 if not os.path.isdir(dest_dir):
255 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 shutil.copy(src, dest)
257 # make the file read-only
258 mode = os.stat(dest)[stat.ST_MODE]
259 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
260 os.chmod(dest, mode)
261 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700262 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Anthony King7bdac712014-07-16 12:56:40 +0100265class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Wink Saville4c426ef2015-06-03 08:05:17 -0700267 def __init__(self, git_worktree, src, dest, relsrc, absdest):
268 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500269 self.src = src
270 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700271 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 self.abs_dest = absdest
273
Wink Saville4c426ef2015-06-03 08:05:17 -0700274 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500275 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 try:
278 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800279 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700280 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500283 if not os.path.isdir(dest_dir):
284 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700285 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500286 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700287 _error('Cannot link file %s to %s', relSrc, absDest)
288
289 def _Link(self):
290 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
291 on the src linking all of the files in the source in to the destination
292 directory.
293 """
294 # We use the absSrc to handle the situation where the current directory
295 # is not the root of the repo
296 absSrc = os.path.join(self.git_worktree, self.src)
297 if os.path.exists(absSrc):
298 # Entity exists so just a simple one to one link operation
299 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
300 else:
301 # Entity doesn't exist assume there is a wild card
302 absDestDir = self.abs_dest
303 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
304 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700305 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700306 else:
307 absSrcFiles = glob.glob(absSrc)
308 for absSrcFile in absSrcFiles:
309 # Create a releative path from source dir to destination dir
310 absSrcDir = os.path.dirname(absSrcFile)
311 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
312
313 # Get the source file name
314 srcFile = os.path.basename(absSrcFile)
315
316 # Now form the final full paths to srcFile. They will be
317 # absolute for the desintaiton and relative for the srouce.
318 absDest = os.path.join(absDestDir, srcFile)
319 relSrc = os.path.join(relSrcDir, srcFile)
320 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500321
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700322
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700323class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700325 def __init__(self,
326 name,
Anthony King7bdac712014-07-16 12:56:40 +0100327 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700328 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100329 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700330 revision=None,
331 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700332 self.name = name
333 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700334 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700335 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100336 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700337 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700339
Doug Anderson37282b42011-03-04 11:54:18 -0800340class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700341
Doug Anderson37282b42011-03-04 11:54:18 -0800342 """A RepoHook contains information about a script to run as a hook.
343
344 Hooks are used to run a python script before running an upload (for instance,
345 to run presubmit checks). Eventually, we may have hooks for other actions.
346
347 This shouldn't be confused with files in the 'repo/hooks' directory. Those
348 files are copied into each '.git/hooks' folder for each project. Repo-level
349 hooks are associated instead with repo actions.
350
351 Hooks are always python. When a hook is run, we will load the hook into the
352 interpreter and execute its main() function.
353 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700354
Doug Anderson37282b42011-03-04 11:54:18 -0800355 def __init__(self,
356 hook_type,
357 hooks_project,
358 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400359 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800360 abort_if_user_denies=False):
361 """RepoHook constructor.
362
363 Params:
364 hook_type: A string representing the type of hook. This is also used
365 to figure out the name of the file containing the hook. For
366 example: 'pre-upload'.
367 hooks_project: The project containing the repo hooks. If you have a
368 manifest, this is manifest.repo_hooks_project. OK if this is None,
369 which will make the hook a no-op.
370 topdir: Repo's top directory (the one containing the .repo directory).
371 Scripts will run with CWD as this directory. If you have a manifest,
372 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400373 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800374 abort_if_user_denies: If True, we'll throw a HookError() if the user
375 doesn't allow us to run the hook.
376 """
377 self._hook_type = hook_type
378 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400379 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800380 self._topdir = topdir
381 self._abort_if_user_denies = abort_if_user_denies
382
383 # Store the full path to the script for convenience.
384 if self._hooks_project:
385 self._script_fullpath = os.path.join(self._hooks_project.worktree,
386 self._hook_type + '.py')
387 else:
388 self._script_fullpath = None
389
390 def _GetHash(self):
391 """Return a hash of the contents of the hooks directory.
392
393 We'll just use git to do this. This hash has the property that if anything
394 changes in the directory we will return a different has.
395
396 SECURITY CONSIDERATION:
397 This hash only represents the contents of files in the hook directory, not
398 any other files imported or called by hooks. Changes to imported files
399 can change the script behavior without affecting the hash.
400
401 Returns:
402 A string representing the hash. This will always be ASCII so that it can
403 be printed to the user easily.
404 """
405 assert self._hooks_project, "Must have hooks to calculate their hash."
406
407 # We will use the work_git object rather than just calling GetRevisionId().
408 # That gives us a hash of the latest checked in version of the files that
409 # the user will actually be executing. Specifically, GetRevisionId()
410 # doesn't appear to change even if a user checks out a different version
411 # of the hooks repo (via git checkout) nor if a user commits their own revs.
412 #
413 # NOTE: Local (non-committed) changes will not be factored into this hash.
414 # I think this is OK, since we're really only worried about warning the user
415 # about upstream changes.
416 return self._hooks_project.work_git.rev_parse('HEAD')
417
418 def _GetMustVerb(self):
419 """Return 'must' if the hook is required; 'should' if not."""
420 if self._abort_if_user_denies:
421 return 'must'
422 else:
423 return 'should'
424
425 def _CheckForHookApproval(self):
426 """Check to see whether this hook has been approved.
427
Mike Frysinger40252c22016-08-15 21:23:44 -0400428 We'll accept approval of manifest URLs if they're using secure transports.
429 This way the user can say they trust the manifest hoster. For insecure
430 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800431
432 Note that we ask permission for each individual hook even though we use
433 the hash of all hooks when detecting changes. We'd like the user to be
434 able to approve / deny each hook individually. We only use the hash of all
435 hooks because there is no other easy way to detect changes to local imports.
436
437 Returns:
438 True if this hook is approved to run; False otherwise.
439
440 Raises:
441 HookError: Raised if the user doesn't approve and abort_if_user_denies
442 was passed to the consturctor.
443 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400444 if self._ManifestUrlHasSecureScheme():
445 return self._CheckForHookApprovalManifest()
446 else:
447 return self._CheckForHookApprovalHash()
448
449 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
450 changed_prompt):
451 """Check for approval for a particular attribute and hook.
452
453 Args:
454 subkey: The git config key under [repo.hooks.<hook_type>] to store the
455 last approved string.
456 new_val: The new value to compare against the last approved one.
457 main_prompt: Message to display to the user to ask for approval.
458 changed_prompt: Message explaining why we're re-asking for approval.
459
460 Returns:
461 True if this hook is approved to run; False otherwise.
462
463 Raises:
464 HookError: Raised if the user doesn't approve and abort_if_user_denies
465 was passed to the consturctor.
466 """
Doug Anderson37282b42011-03-04 11:54:18 -0800467 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800469
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 # Get the last value that the user approved for this hook; may be None.
471 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800472
Mike Frysinger40252c22016-08-15 21:23:44 -0400473 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800474 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400475 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800476 # Approval matched. We're done.
477 return True
478 else:
479 # Give the user a reason why we're prompting, since they last told
480 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400481 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800482 else:
483 prompt = ''
484
485 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
486 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400487 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530488 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900489 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800490
491 # User is doing a one-time approval.
492 if response in ('y', 'yes'):
493 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400494 elif response == 'always':
495 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800496 return True
497
498 # For anything else, we'll assume no approval.
499 if self._abort_if_user_denies:
500 raise HookError('You must allow the %s hook or use --no-verify.' %
501 self._hook_type)
502
503 return False
504
Mike Frysinger40252c22016-08-15 21:23:44 -0400505 def _ManifestUrlHasSecureScheme(self):
506 """Check if the URI for the manifest is a secure transport."""
507 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
508 parse_results = urllib.parse.urlparse(self._manifest_url)
509 return parse_results.scheme in secure_schemes
510
511 def _CheckForHookApprovalManifest(self):
512 """Check whether the user has approved this manifest host.
513
514 Returns:
515 True if this hook is approved to run; False otherwise.
516 """
517 return self._CheckForHookApprovalHelper(
518 'approvedmanifest',
519 self._manifest_url,
520 'Run hook scripts from %s' % (self._manifest_url,),
521 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
522
523 def _CheckForHookApprovalHash(self):
524 """Check whether the user has approved the hooks repo.
525
526 Returns:
527 True if this hook is approved to run; False otherwise.
528 """
529 prompt = ('Repo %s run the script:\n'
530 ' %s\n'
531 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700532 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400533 return self._CheckForHookApprovalHelper(
534 'approvedhash',
535 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700536 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400537 'Scripts have changed since %s was allowed.' % (self._hook_type,))
538
Doug Anderson37282b42011-03-04 11:54:18 -0800539 def _ExecuteHook(self, **kwargs):
540 """Actually execute the given hook.
541
542 This will run the hook's 'main' function in our python interpreter.
543
544 Args:
545 kwargs: Keyword arguments to pass to the hook. These are often specific
546 to the hook type. For instance, pre-upload hooks will contain
547 a project_list.
548 """
549 # Keep sys.path and CWD stashed away so that we can always restore them
550 # upon function exit.
551 orig_path = os.getcwd()
552 orig_syspath = sys.path
553
554 try:
555 # Always run hooks with CWD as topdir.
556 os.chdir(self._topdir)
557
558 # Put the hook dir as the first item of sys.path so hooks can do
559 # relative imports. We want to replace the repo dir as [0] so
560 # hooks can't import repo files.
561 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
562
563 # Exec, storing global context in the context dict. We catch exceptions
564 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500565 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800566 try:
Anthony King70f68902014-05-05 21:15:34 +0100567 exec(compile(open(self._script_fullpath).read(),
568 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800569 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700570 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
571 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800572
573 # Running the script should have defined a main() function.
574 if 'main' not in context:
575 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
576
Doug Anderson37282b42011-03-04 11:54:18 -0800577 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
578 # We don't actually want hooks to define their main with this argument--
579 # it's there to remind them that their hook should always take **kwargs.
580 # For instance, a pre-upload hook should be defined like:
581 # def main(project_list, **kwargs):
582 #
583 # This allows us to later expand the API without breaking old hooks.
584 kwargs = kwargs.copy()
585 kwargs['hook_should_take_kwargs'] = True
586
587 # Call the main function in the hook. If the hook should cause the
588 # build to fail, it will raise an Exception. We'll catch that convert
589 # to a HookError w/ just the failing traceback.
590 try:
591 context['main'](**kwargs)
592 except Exception:
593 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700594 'above.' % (traceback.format_exc(),
595 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800596 finally:
597 # Restore sys.path and CWD.
598 sys.path = orig_syspath
599 os.chdir(orig_path)
600
601 def Run(self, user_allows_all_hooks, **kwargs):
602 """Run the hook.
603
604 If the hook doesn't exist (because there is no hooks project or because
605 this particular hook is not enabled), this is a no-op.
606
607 Args:
608 user_allows_all_hooks: If True, we will never prompt about running the
609 hook--we'll just assume it's OK to run it.
610 kwargs: Keyword arguments to pass to the hook. These are often specific
611 to the hook type. For instance, pre-upload hooks will contain
612 a project_list.
613
614 Raises:
615 HookError: If there was a problem finding the hook or the user declined
616 to run a required hook (from _CheckForHookApproval).
617 """
618 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700619 if ((not self._hooks_project) or (self._hook_type not in
620 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800621 return
622
623 # Bail with a nice error if we can't find the hook.
624 if not os.path.isfile(self._script_fullpath):
625 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
626
627 # Make sure the user is OK with running the hook.
628 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
629 return
630
631 # Run the hook with the same version of python we're using.
632 self._ExecuteHook(**kwargs)
633
634
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600636 # These objects can be shared between several working trees.
637 shareable_files = ['description', 'info']
638 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
639 # These objects can only be used by a single working tree.
640 working_tree_files = ['config', 'packed-refs', 'shallow']
641 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700642
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643 def __init__(self,
644 manifest,
645 name,
646 remote,
647 gitdir,
David James8d201162013-10-11 17:03:19 -0700648 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700649 worktree,
650 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700651 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800652 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100653 rebase=True,
654 groups=None,
655 sync_c=False,
656 sync_s=False,
657 clone_depth=None,
658 upstream=None,
659 parent=None,
660 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900661 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700662 optimized_fetch=False,
663 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800664 """Init a Project object.
665
666 Args:
667 manifest: The XmlManifest object.
668 name: The `name` attribute of manifest.xml's project element.
669 remote: RemoteSpec object specifying its remote's properties.
670 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700671 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800672 worktree: Absolute path of git working tree.
673 relpath: Relative path of git working tree to repo's top directory.
674 revisionExpr: The `revision` attribute of manifest.xml's project element.
675 revisionId: git commit id for checking out.
676 rebase: The `rebase` attribute of manifest.xml's project element.
677 groups: The `groups` attribute of manifest.xml's project element.
678 sync_c: The `sync-c` attribute of manifest.xml's project element.
679 sync_s: The `sync-s` attribute of manifest.xml's project element.
680 upstream: The `upstream` attribute of manifest.xml's project element.
681 parent: The parent Project object.
682 is_derived: False if the project was explicitly defined in the manifest;
683 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400684 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900685 optimized_fetch: If True, when a project is set to a sha1 revision, only
686 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700687 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800688 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 self.manifest = manifest
690 self.name = name
691 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800692 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700693 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800694 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700695 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800696 else:
697 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700699 self.revisionExpr = revisionExpr
700
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700701 if revisionId is None \
702 and revisionExpr \
703 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700704 self.revisionId = revisionExpr
705 else:
706 self.revisionId = revisionId
707
Mike Pontillod3153822012-02-28 11:53:24 -0800708 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700709 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700710 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800711 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900712 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700713 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800714 self.parent = parent
715 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900716 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800717 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800718
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500721 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500722 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700723 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
724 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800726 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700727 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800728 else:
729 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700730 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700731 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700732 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400733 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700734 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
Doug Anderson37282b42011-03-04 11:54:18 -0800736 # This will be filled in if a project is later identified to be the
737 # project containing repo hooks.
738 self.enabled_repo_hooks = []
739
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800741 def Derived(self):
742 return self.is_derived
743
744 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600746 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747
748 @property
749 def CurrentBranch(self):
750 """Obtain the name of the currently checked out branch.
751 The branch name omits the 'refs/heads/' prefix.
752 None is returned if the project is on a detached HEAD.
753 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700754 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 if b.startswith(R_HEADS):
756 return b[len(R_HEADS):]
757 return None
758
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700759 def IsRebaseInProgress(self):
760 w = self.worktree
761 g = os.path.join(w, '.git')
762 return os.path.exists(os.path.join(g, 'rebase-apply')) \
763 or os.path.exists(os.path.join(g, 'rebase-merge')) \
764 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 def IsDirty(self, consider_untracked=True):
767 """Is the working directory modified in some way?
768 """
769 self.work_git.update_index('-q',
770 '--unmerged',
771 '--ignore-missing',
772 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900773 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774 return True
775 if self.work_git.DiffZ('diff-files'):
776 return True
777 if consider_untracked and self.work_git.LsOthers():
778 return True
779 return False
780
781 _userident_name = None
782 _userident_email = None
783
784 @property
785 def UserName(self):
786 """Obtain the user's personal name.
787 """
788 if self._userident_name is None:
789 self._LoadUserIdentity()
790 return self._userident_name
791
792 @property
793 def UserEmail(self):
794 """Obtain the user's email address. This is very likely
795 to be their Gerrit login.
796 """
797 if self._userident_email is None:
798 self._LoadUserIdentity()
799 return self._userident_email
800
801 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900802 u = self.bare_git.var('GIT_COMMITTER_IDENT')
803 m = re.compile("^(.*) <([^>]*)> ").match(u)
804 if m:
805 self._userident_name = m.group(1)
806 self._userident_email = m.group(2)
807 else:
808 self._userident_name = ''
809 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810
811 def GetRemote(self, name):
812 """Get the configuration for a single remote.
813 """
814 return self.config.GetRemote(name)
815
816 def GetBranch(self, name):
817 """Get the configuration for a single branch.
818 """
819 return self.config.GetBranch(name)
820
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700821 def GetBranches(self):
822 """Get all existing local branches.
823 """
824 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900825 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700826 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700827
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530828 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700829 if name.startswith(R_HEADS):
830 name = name[len(R_HEADS):]
831 b = self.GetBranch(name)
832 b.current = name == current
833 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900834 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700835 heads[name] = b
836
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530837 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700838 if name.startswith(R_PUB):
839 name = name[len(R_PUB):]
840 b = heads.get(name)
841 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900842 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700843
844 return heads
845
Colin Cross5acde752012-03-28 20:15:45 -0700846 def MatchesGroups(self, manifest_groups):
847 """Returns true if the manifest groups specified at init should cause
848 this project to be synced.
849 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700850 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700851
852 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700853 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700854 manifest_groups: "-group1,group2"
855 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500856
857 The special manifest group "default" will match any project that
858 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700859 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500860 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700861 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700862 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500863 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700864
Conley Owens971de8e2012-04-16 10:36:08 -0700865 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700866 for group in expanded_manifest_groups:
867 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700868 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700869 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700870 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700871
Conley Owens971de8e2012-04-16 10:36:08 -0700872 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700874# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700875 def UncommitedFiles(self, get_all=True):
876 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700878 Args:
879 get_all: a boolean, if True - get information about all different
880 uncommitted files. If False - return as soon as any kind of
881 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500882 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700883 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500884 self.work_git.update_index('-q',
885 '--unmerged',
886 '--ignore-missing',
887 '--refresh')
888 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700889 details.append("rebase in progress")
890 if not get_all:
891 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500892
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700893 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
894 if changes:
895 details.extend(changes)
896 if not get_all:
897 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500898
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700899 changes = self.work_git.DiffZ('diff-files').keys()
900 if changes:
901 details.extend(changes)
902 if not get_all:
903 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500904
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700905 changes = self.work_git.LsOthers()
906 if changes:
907 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500908
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700909 return details
910
911 def HasChanges(self):
912 """Returns true if there are uncommitted changes.
913 """
914 if self.UncommitedFiles(get_all=False):
915 return True
916 else:
917 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500918
Terence Haddock4655e812011-03-31 12:33:34 +0200919 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200921
922 Args:
923 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """
925 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700926 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200927 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700928 print(file=output_redir)
929 print('project %s/' % self.relpath, file=output_redir)
930 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 return
932
933 self.work_git.update_index('-q',
934 '--unmerged',
935 '--ignore-missing',
936 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700937 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
939 df = self.work_git.DiffZ('diff-files')
940 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100941 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700942 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
944 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700945 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200946 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700947 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
949 branch = self.CurrentBranch
950 if branch is None:
951 out.nobranch('(*** NO BRANCH ***)')
952 else:
953 out.branch('branch %s', branch)
954 out.nl()
955
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700956 if rb:
957 out.important('prior sync failed; rebase still in progress')
958 out.nl()
959
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 paths = list()
961 paths.extend(di.keys())
962 paths.extend(df.keys())
963 paths.extend(do)
964
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530965 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900966 try:
967 i = di[p]
968 except KeyError:
969 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900971 try:
972 f = df[p]
973 except KeyError:
974 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200975
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900976 if i:
977 i_status = i.status.upper()
978 else:
979 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900981 if f:
982 f_status = f.status.lower()
983 else:
984 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985
986 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800987 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700988 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 else:
990 line = ' %s%s\t%s' % (i_status, f_status, p)
991
992 if i and not f:
993 out.added('%s', line)
994 elif (i and f) or (not i and f):
995 out.changed('%s', line)
996 elif not i and not f:
997 out.untracked('%s', line)
998 else:
999 out.write('%s', line)
1000 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001001
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001002 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003
pelyad67872d2012-03-28 14:49:58 +03001004 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 """Prints the status of the repository to stdout.
1006 """
1007 out = DiffColoring(self.config)
1008 cmd = ['diff']
1009 if out.is_on:
1010 cmd.append('--color')
1011 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001012 if absolute_paths:
1013 cmd.append('--src-prefix=a/%s/' % self.relpath)
1014 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 cmd.append('--')
1016 p = GitCommand(self,
1017 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001018 capture_stdout=True,
1019 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 has_diff = False
1021 for line in p.process.stdout:
1022 if not has_diff:
1023 out.nl()
1024 out.project('project %s/' % self.relpath)
1025 out.nl()
1026 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001027 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 p.Wait()
1029
1030
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001031# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032
David Pursehouse8a68ff92012-09-24 12:15:13 +09001033 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034 """Was the branch published (uploaded) for code review?
1035 If so, returns the SHA-1 hash of the last published
1036 state for the branch.
1037 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001038 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001039 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001040 try:
1041 return self.bare_git.rev_parse(key)
1042 except GitError:
1043 return None
1044 else:
1045 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001046 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001047 except KeyError:
1048 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049
David Pursehouse8a68ff92012-09-24 12:15:13 +09001050 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 """Prunes any stale published refs.
1052 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001053 if all_refs is None:
1054 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 heads = set()
1056 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301057 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 if name.startswith(R_HEADS):
1059 heads.add(name)
1060 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001061 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301063 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 n = name[len(R_PUB):]
1065 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001066 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001068 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 """List any branches which can be uploaded for review.
1070 """
1071 heads = {}
1072 pubed = {}
1073
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301074 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001076 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001078 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079
1080 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301081 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001082 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001084 if selected_branch and branch != selected_branch:
1085 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001087 rb = self.GetUploadableBranch(branch)
1088 if rb:
1089 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 return ready
1091
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001092 def GetUploadableBranch(self, branch_name):
1093 """Get a single uploadable branch, or None.
1094 """
1095 branch = self.GetBranch(branch_name)
1096 base = branch.LocalMerge
1097 if branch.LocalMerge:
1098 rb = ReviewableBranch(self, branch, base)
1099 if rb.commits:
1100 return rb
1101 return None
1102
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001103 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001104 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001105 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001106 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001107 private=False,
1108 wip=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001109 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110 """Uploads the named branch for code review.
1111 """
1112 if branch is None:
1113 branch = self.CurrentBranch
1114 if branch is None:
1115 raise GitError('not currently on a branch')
1116
1117 branch = self.GetBranch(branch)
1118 if not branch.LocalMerge:
1119 raise GitError('branch %s does not track a remote' % branch.name)
1120 if not branch.remote.review:
1121 raise GitError('remote %s has no review url' % branch.remote.name)
1122
Bryan Jacobsf609f912013-05-06 13:36:24 -04001123 if dest_branch is None:
1124 dest_branch = self.dest_branch
1125 if dest_branch is None:
1126 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127 if not dest_branch.startswith(R_HEADS):
1128 dest_branch = R_HEADS + dest_branch
1129
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001130 if not branch.remote.projectname:
1131 branch.remote.projectname = self.name
1132 branch.remote.Save()
1133
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001134 url = branch.remote.ReviewUrl(self.UserEmail)
1135 if url is None:
1136 raise UploadError('review not configured')
1137 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001138
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001139 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001140 rp = ['gerrit receive-pack']
1141 for e in people[0]:
1142 rp.append('--reviewer=%s' % sq(e))
1143 for e in people[1]:
1144 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001145 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001146
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001147 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001148
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001149 if dest_branch.startswith(R_HEADS):
1150 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001151
1152 upload_type = 'for'
1153 if draft:
1154 upload_type = 'drafts'
1155
1156 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1157 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001158 if auto_topic:
1159 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001160
Shawn Pearce45d21682013-02-28 00:35:51 -08001161 if not url.startswith('ssh://'):
1162 rp = ['r=%s' % p for p in people[0]] + \
1163 ['cc=%s' % p for p in people[1]]
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001164 if private:
1165 rp = rp + ['private']
1166 if wip:
1167 rp = rp + ['wip']
Shawn Pearce45d21682013-02-28 00:35:51 -08001168 if rp:
1169 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001170 cmd.append(ref_spec)
1171
Anthony King7bdac712014-07-16 12:56:40 +01001172 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001173 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174
1175 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1176 self.bare_git.UpdateRef(R_PUB + branch.name,
1177 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001178 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
1180
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001181# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182
Julien Campergue335f5ef2013-10-16 11:02:35 +02001183 def _ExtractArchive(self, tarpath, path=None):
1184 """Extract the given tar on its current location
1185
1186 Args:
1187 - tarpath: The path to the actual tar file
1188
1189 """
1190 try:
1191 with tarfile.open(tarpath, 'r') as tar:
1192 tar.extractall(path=path)
1193 return True
1194 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001195 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001196 return False
1197
Ningning Xiac2fbc782016-08-22 14:24:39 -07001198 def CachePopulate(self, cache_dir, url):
1199 """Populate cache in the cache_dir.
1200
1201 Args:
1202 cache_dir: Directory to cache git files from Google Storage.
1203 url: Git url of current repository.
1204
1205 Raises:
1206 CacheApplyError if it fails to populate the git cache.
1207 """
1208 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1209 '--cache-dir', cache_dir, url]
1210
1211 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1212 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1213 'url: %s' % (cache_dir, url))
1214
1215 def CacheExists(self, cache_dir, url):
1216 """Check the existence of the cache files.
1217
1218 Args:
1219 cache_dir: Directory to cache git files.
1220 url: Git url of current repository.
1221
1222 Raises:
1223 CacheApplyError if the cache files do not exist.
1224 """
1225 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1226
1227 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1228 if exist.Wait() != 0:
1229 raise CacheApplyError('Failed to execute git cache exists cmd. '
1230 'cache_dir: %s url: %s' % (cache_dir, url))
1231
1232 if not exist.stdout or not exist.stdout.strip():
1233 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1234 'url: %s' % (cache_dir, url))
1235 return exist.stdout.strip()
1236
1237 def CacheApply(self, cache_dir):
1238 """Apply git cache files populated from Google Storage buckets.
1239
1240 Args:
1241 cache_dir: Directory to cache git files.
1242
1243 Raises:
1244 CacheApplyError if it fails to apply git caches.
1245 """
1246 remote = self.GetRemote(self.remote.name)
1247
1248 self.CachePopulate(cache_dir, remote.url)
1249
1250 mirror_dir = self.CacheExists(cache_dir, remote.url)
1251
1252 refspec = RefSpec(True, 'refs/heads/*',
1253 'refs/remotes/%s/*' % remote.name)
1254
1255 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1256 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1257 raise CacheApplyError('Failed to fetch refs %s from %s' %
1258 (mirror_dir, str(refspec)))
1259
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001260 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001261 quiet=False,
1262 is_new=None,
1263 current_branch_only=False,
1264 force_sync=False,
1265 clone_bundle=True,
1266 no_tags=False,
1267 archive=False,
1268 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001269 prune=False,
1270 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 """Perform only the network IO portion of the sync process.
1272 Local working directory/branch state is not affected.
1273 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001274 if archive and not isinstance(self, MetaProject):
1275 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001276 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001277 return False
1278
1279 name = self.relpath.replace('\\', '/')
1280 name = name.replace('/', '_')
1281 tarpath = '%s.tar' % name
1282 topdir = self.manifest.topdir
1283
1284 try:
1285 self._FetchArchive(tarpath, cwd=topdir)
1286 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001287 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001288 return False
1289
1290 # From now on, we only need absolute tarpath
1291 tarpath = os.path.join(topdir, tarpath)
1292
1293 if not self._ExtractArchive(tarpath, path=topdir):
1294 return False
1295 try:
1296 os.remove(tarpath)
1297 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001298 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001299 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001300 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001301 if is_new is None:
1302 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001303 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001304 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001305 else:
1306 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001308
1309 if is_new:
1310 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1311 try:
1312 fd = open(alt, 'rb')
1313 try:
1314 alt_dir = fd.readline().rstrip()
1315 finally:
1316 fd.close()
1317 except IOError:
1318 alt_dir = None
1319 else:
1320 alt_dir = None
1321
Ningning Xiac2fbc782016-08-22 14:24:39 -07001322 applied_cache = False
1323 # If cache_dir is provided, and it's a new repository without
1324 # alternative_dir, bootstrap this project repo with the git
1325 # cache files.
1326 if cache_dir is not None and is_new and alt_dir is None:
1327 try:
1328 self.CacheApply(cache_dir)
1329 applied_cache = True
1330 is_new = False
1331 except CacheApplyError as e:
1332 _error('Could not apply git cache: %s', e)
1333 _error('Please check if you have the right GS credentials.')
1334 _error('Please check if the cache files exist in GS.')
1335
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001336 if clone_bundle \
Ningning Xiac2fbc782016-08-22 14:24:39 -07001337 and not applied_cache \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001338 and alt_dir is None \
1339 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001340 is_new = False
1341
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001342 if not current_branch_only:
1343 if self.sync_c:
1344 current_branch_only = True
1345 elif not self.manifest._loaded:
1346 # Manifest cannot check defaults until it syncs.
1347 current_branch_only = False
1348 elif self.manifest.default.sync_c:
1349 current_branch_only = True
1350
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001351 need_to_fetch = not (optimized_fetch and
1352 (ID_RE.match(self.revisionExpr) and
1353 self._CheckForSha1()))
1354 if (need_to_fetch and
1355 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1356 current_branch_only=current_branch_only,
1357 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001358 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001359
1360 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001361 self._InitMRef()
1362 else:
1363 self._InitMirrorHead()
1364 try:
1365 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1366 except OSError:
1367 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001369
1370 def PostRepoUpgrade(self):
1371 self._InitHooks()
1372
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001373 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001374 if self.manifest.isGitcClient:
1375 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001376 for copyfile in self.copyfiles:
1377 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001378 for linkfile in self.linkfiles:
1379 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380
Julien Camperguedd654222014-01-09 16:21:37 +01001381 def GetCommitRevisionId(self):
1382 """Get revisionId of a commit.
1383
1384 Use this method instead of GetRevisionId to get the id of the commit rather
1385 than the id of the current git object (for example, a tag)
1386
1387 """
1388 if not self.revisionExpr.startswith(R_TAGS):
1389 return self.GetRevisionId(self._allrefs)
1390
1391 try:
1392 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1393 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001394 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1395 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001396
David Pursehouse8a68ff92012-09-24 12:15:13 +09001397 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001398 if self.revisionId:
1399 return self.revisionId
1400
1401 rem = self.GetRemote(self.remote.name)
1402 rev = rem.ToLocal(self.revisionExpr)
1403
David Pursehouse8a68ff92012-09-24 12:15:13 +09001404 if all_refs is not None and rev in all_refs:
1405 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001406
1407 try:
1408 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1409 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001410 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1411 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001412
Kevin Degiabaa7f32014-11-12 11:27:45 -07001413 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001414 """Perform only the local IO portion of the sync process.
1415 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001416 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001417 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001418 all_refs = self.bare_ref.all
1419 self.CleanPublishedCache(all_refs)
1420 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001421
David Pursehouse1d947b32012-10-25 12:23:11 +09001422 def _doff():
1423 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001424 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001425
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001426 head = self.work_git.GetHead()
1427 if head.startswith(R_HEADS):
1428 branch = head[len(R_HEADS):]
1429 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001430 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001431 except KeyError:
1432 head = None
1433 else:
1434 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001436 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001437 # Currently on a detached HEAD. The user is assumed to
1438 # not have any local modifications worth worrying about.
1439 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001440 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001441 syncbuf.fail(self, _PriorSyncFailedError())
1442 return
1443
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001444 if head == revid:
1445 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001446 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001447 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001448 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001449 # The copy/linkfile config may have changed.
1450 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001451 return
1452 else:
1453 lost = self._revlist(not_rev(revid), HEAD)
1454 if lost:
1455 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001456
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001458 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001459 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001460 syncbuf.fail(self, e)
1461 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001462 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001463 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001464
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001465 if head == revid:
1466 # No changes; don't do anything further.
1467 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001468 # The copy/linkfile config may have changed.
1469 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001470 return
1471
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001474 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001475 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001476 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001477 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001478 syncbuf.info(self,
1479 "leaving %s; does not track upstream",
1480 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001481 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001482 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001483 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001484 syncbuf.fail(self, e)
1485 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001486 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001487 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001488
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001489 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001490 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001493 if not_merged:
1494 if upstream_gain:
1495 # The user has published this branch and some of those
1496 # commits are not yet merged upstream. We do not want
1497 # to rewrite the published commits so we punt.
1498 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001499 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001500 "branch %s is published (but not merged) and is now "
1501 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001502 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001503 elif pub == head:
1504 # All published commits are merged, and thus we are a
1505 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001506 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001507 syncbuf.later1(self, _doff)
1508 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001509
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001510 # Examine the local commits not in the remote. Find the
1511 # last one attributed to this user, if any.
1512 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001513 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001514 last_mine = None
1515 cnt_mine = 0
1516 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301517 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001518 if committer_email == self.UserEmail:
1519 last_mine = commit_id
1520 cnt_mine += 1
1521
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001522 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001523 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524
1525 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001526 syncbuf.fail(self, _DirtyError())
1527 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001529 # If the upstream switched on us, warn the user.
1530 #
1531 if branch.merge != self.revisionExpr:
1532 if branch.merge and self.revisionExpr:
1533 syncbuf.info(self,
1534 'manifest switched %s...%s',
1535 branch.merge,
1536 self.revisionExpr)
1537 elif branch.merge:
1538 syncbuf.info(self,
1539 'manifest no longer tracks %s',
1540 branch.merge)
1541
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001542 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001543 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001544 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001545 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001546 syncbuf.info(self,
1547 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001548 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001549
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001550 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001551 if not ID_RE.match(self.revisionExpr):
1552 # in case of manifest sync the revisionExpr might be a SHA1
1553 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001554 if not branch.merge.startswith('refs/'):
1555 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556 branch.Save()
1557
Mike Pontillod3153822012-02-28 11:53:24 -08001558 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001559 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001560 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001561 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001562 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001563 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001565 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001566 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001567 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001568 syncbuf.fail(self, e)
1569 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001571 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001573 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574 # dest should already be an absolute path, but src is project relative
1575 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001576 abssrc = os.path.join(self.worktree, src)
1577 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001578
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001579 def AddLinkFile(self, src, dest, absdest):
1580 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001581 # make src relative path to dest
1582 absdestdir = os.path.dirname(absdest)
1583 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001584 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001585
James W. Mills24c13082012-04-12 15:04:13 -05001586 def AddAnnotation(self, name, value, keep):
1587 self.annotations.append(_Annotation(name, value, keep))
1588
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001589 def DownloadPatchSet(self, change_id, patch_id):
1590 """Download a single patch set of a single change to FETCH_HEAD.
1591 """
1592 remote = self.GetRemote(self.remote.name)
1593
1594 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001595 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001596 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001597 if GitCommand(self, cmd, bare=True).Wait() != 0:
1598 return None
1599 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001600 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001601 change_id,
1602 patch_id,
1603 self.bare_git.rev_parse('FETCH_HEAD'))
1604
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001606# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001607
Simran Basib9a1b732015-08-20 12:19:28 -07001608 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609 """Create a new branch off the manifest's revision.
1610 """
Simran Basib9a1b732015-08-20 12:19:28 -07001611 if not branch_merge:
1612 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001613 head = self.work_git.GetHead()
1614 if head == (R_HEADS + name):
1615 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001616
David Pursehouse8a68ff92012-09-24 12:15:13 +09001617 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001618 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001619 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001620 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001621 capture_stdout=True,
1622 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001623
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001624 branch = self.GetBranch(name)
1625 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001626 branch.merge = branch_merge
1627 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1628 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001629 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001630
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001631 if head.startswith(R_HEADS):
1632 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001633 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001634 except KeyError:
1635 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001636 if revid and head and revid == head:
1637 ref = os.path.join(self.gitdir, R_HEADS + name)
1638 try:
1639 os.makedirs(os.path.dirname(ref))
1640 except OSError:
1641 pass
1642 _lwrite(ref, '%s\n' % revid)
1643 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1644 'ref: %s%s\n' % (R_HEADS, name))
1645 branch.Save()
1646 return True
1647
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001648 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001649 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001650 capture_stdout=True,
1651 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001652 branch.Save()
1653 return True
1654 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001655
Wink Saville02d79452009-04-10 13:01:24 -07001656 def CheckoutBranch(self, name):
1657 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001658
1659 Args:
1660 name: The name of the branch to checkout.
1661
1662 Returns:
1663 True if the checkout succeeded; False if it didn't; None if the branch
1664 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001665 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001666 rev = R_HEADS + name
1667 head = self.work_git.GetHead()
1668 if head == rev:
1669 # Already on the branch
1670 #
1671 return True
Wink Saville02d79452009-04-10 13:01:24 -07001672
David Pursehouse8a68ff92012-09-24 12:15:13 +09001673 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001674 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001675 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001676 except KeyError:
1677 # Branch does not exist in this project
1678 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001679 return None
Wink Saville02d79452009-04-10 13:01:24 -07001680
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001681 if head.startswith(R_HEADS):
1682 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001683 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001684 except KeyError:
1685 head = None
1686
1687 if head == revid:
1688 # Same revision; just update HEAD to point to the new
1689 # target branch, but otherwise take no other action.
1690 #
1691 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1692 'ref: %s%s\n' % (R_HEADS, name))
1693 return True
1694
1695 return GitCommand(self,
1696 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001697 capture_stdout=True,
1698 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001699
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001700 def AbandonBranch(self, name):
1701 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001702
1703 Args:
1704 name: The name of the branch to abandon.
1705
1706 Returns:
1707 True if the abandon succeeded; False if it didn't; None if the branch
1708 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001709 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001710 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001711 all_refs = self.bare_ref.all
1712 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001713 # Doesn't exist
1714 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001715
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001716 head = self.work_git.GetHead()
1717 if head == rev:
1718 # We can't destroy the branch while we are sitting
1719 # on it. Switch to a detached HEAD.
1720 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001721 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001722
David Pursehouse8a68ff92012-09-24 12:15:13 +09001723 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001724 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001725 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1726 '%s\n' % revid)
1727 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001728 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001729
1730 return GitCommand(self,
1731 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001732 capture_stdout=True,
1733 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 def PruneHeads(self):
1736 """Prune any topic branches already merged into upstream.
1737 """
1738 cb = self.CurrentBranch
1739 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001740 left = self._allrefs
1741 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742 if name.startswith(R_HEADS):
1743 name = name[len(R_HEADS):]
1744 if cb is None or name != cb:
1745 kill.append(name)
1746
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001747 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748 if cb is not None \
1749 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001750 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751 self.work_git.DetachHead(HEAD)
1752 kill.append(cb)
1753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001755 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757 try:
1758 self.bare_git.DetachHead(rev)
1759
1760 b = ['branch', '-d']
1761 b.extend(kill)
1762 b = GitCommand(self, b, bare=True,
1763 capture_stdout=True,
1764 capture_stderr=True)
1765 b.Wait()
1766 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001767 if ID_RE.match(old):
1768 self.bare_git.DetachHead(old)
1769 else:
1770 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001771 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001773 for branch in kill:
1774 if (R_HEADS + branch) not in left:
1775 self.CleanPublishedCache()
1776 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
1778 if cb and cb not in kill:
1779 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001780 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781
1782 kept = []
1783 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001784 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785 branch = self.GetBranch(branch)
1786 base = branch.LocalMerge
1787 if not base:
1788 base = rev
1789 kept.append(ReviewableBranch(self, branch, base))
1790 return kept
1791
1792
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001793# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001794
1795 def GetRegisteredSubprojects(self):
1796 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001797
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001798 def rec(subprojects):
1799 if not subprojects:
1800 return
1801 result.extend(subprojects)
1802 for p in subprojects:
1803 rec(p.subprojects)
1804 rec(self.subprojects)
1805 return result
1806
1807 def _GetSubmodules(self):
1808 # Unfortunately we cannot call `git submodule status --recursive` here
1809 # because the working tree might not exist yet, and it cannot be used
1810 # without a working tree in its current implementation.
1811
1812 def get_submodules(gitdir, rev):
1813 # Parse .gitmodules for submodule sub_paths and sub_urls
1814 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1815 if not sub_paths:
1816 return []
1817 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1818 # revision of submodule repository
1819 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1820 submodules = []
1821 for sub_path, sub_url in zip(sub_paths, sub_urls):
1822 try:
1823 sub_rev = sub_revs[sub_path]
1824 except KeyError:
1825 # Ignore non-exist submodules
1826 continue
1827 submodules.append((sub_rev, sub_path, sub_url))
1828 return submodules
1829
1830 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1831 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001832
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001833 def parse_gitmodules(gitdir, rev):
1834 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1835 try:
Anthony King7bdac712014-07-16 12:56:40 +01001836 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1837 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001838 except GitError:
1839 return [], []
1840 if p.Wait() != 0:
1841 return [], []
1842
1843 gitmodules_lines = []
1844 fd, temp_gitmodules_path = tempfile.mkstemp()
1845 try:
1846 os.write(fd, p.stdout)
1847 os.close(fd)
1848 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001849 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1850 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001851 if p.Wait() != 0:
1852 return [], []
1853 gitmodules_lines = p.stdout.split('\n')
1854 except GitError:
1855 return [], []
1856 finally:
1857 os.remove(temp_gitmodules_path)
1858
1859 names = set()
1860 paths = {}
1861 urls = {}
1862 for line in gitmodules_lines:
1863 if not line:
1864 continue
1865 m = re_path.match(line)
1866 if m:
1867 names.add(m.group(1))
1868 paths[m.group(1)] = m.group(2)
1869 continue
1870 m = re_url.match(line)
1871 if m:
1872 names.add(m.group(1))
1873 urls[m.group(1)] = m.group(2)
1874 continue
1875 names = sorted(names)
1876 return ([paths.get(name, '') for name in names],
1877 [urls.get(name, '') for name in names])
1878
1879 def git_ls_tree(gitdir, rev, paths):
1880 cmd = ['ls-tree', rev, '--']
1881 cmd.extend(paths)
1882 try:
Anthony King7bdac712014-07-16 12:56:40 +01001883 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1884 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001885 except GitError:
1886 return []
1887 if p.Wait() != 0:
1888 return []
1889 objects = {}
1890 for line in p.stdout.split('\n'):
1891 if not line.strip():
1892 continue
1893 object_rev, object_path = line.split()[2:4]
1894 objects[object_path] = object_rev
1895 return objects
1896
1897 try:
1898 rev = self.GetRevisionId()
1899 except GitError:
1900 return []
1901 return get_submodules(self.gitdir, rev)
1902
1903 def GetDerivedSubprojects(self):
1904 result = []
1905 if not self.Exists:
1906 # If git repo does not exist yet, querying its submodules will
1907 # mess up its states; so return here.
1908 return result
1909 for rev, path, url in self._GetSubmodules():
1910 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001911 relpath, worktree, gitdir, objdir = \
1912 self.manifest.GetSubprojectPaths(self, name, path)
1913 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001914 if project:
1915 result.extend(project.GetDerivedSubprojects())
1916 continue
David James8d201162013-10-11 17:03:19 -07001917
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001918 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001919 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001920 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001921 review=self.remote.review,
1922 revision=self.remote.revision)
1923 subproject = Project(manifest=self.manifest,
1924 name=name,
1925 remote=remote,
1926 gitdir=gitdir,
1927 objdir=objdir,
1928 worktree=worktree,
1929 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001930 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001931 revisionId=rev,
1932 rebase=self.rebase,
1933 groups=self.groups,
1934 sync_c=self.sync_c,
1935 sync_s=self.sync_s,
1936 parent=self,
1937 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001938 result.append(subproject)
1939 result.extend(subproject.GetDerivedSubprojects())
1940 return result
1941
1942
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001943# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001944 def _CheckForSha1(self):
1945 try:
1946 # if revision (sha or tag) is not present then following function
1947 # throws an error.
1948 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1949 return True
1950 except GitError:
1951 # There is no such persistent revision. We have to fetch it.
1952 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001953
Julien Campergue335f5ef2013-10-16 11:02:35 +02001954 def _FetchArchive(self, tarpath, cwd=None):
1955 cmd = ['archive', '-v', '-o', tarpath]
1956 cmd.append('--remote=%s' % self.remote.url)
1957 cmd.append('--prefix=%s/' % self.relpath)
1958 cmd.append(self.revisionExpr)
1959
1960 command = GitCommand(self, cmd, cwd=cwd,
1961 capture_stdout=True,
1962 capture_stderr=True)
1963
1964 if command.Wait() != 0:
1965 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1966
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001967 def _RemoteFetch(self, name=None,
1968 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001969 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001970 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001971 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001972 no_tags=False,
1973 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001974
1975 is_sha1 = False
1976 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001977 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001978
David Pursehouse9bc422f2014-04-15 10:28:56 +09001979 # The depth should not be used when fetching to a mirror because
1980 # it will result in a shallow repository that cannot be cloned or
1981 # fetched from.
1982 if not self.manifest.IsMirror:
1983 if self.clone_depth:
1984 depth = self.clone_depth
1985 else:
1986 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001987 # The repo project should never be synced with partial depth
1988 if self.relpath == '.repo/repo':
1989 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001990
Shawn Pearce69e04d82014-01-29 12:48:54 -08001991 if depth:
1992 current_branch_only = True
1993
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001994 if ID_RE.match(self.revisionExpr) is not None:
1995 is_sha1 = True
1996
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001997 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001998 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001999 # this is a tag and its sha1 value should never change
2000 tag_name = self.revisionExpr[len(R_TAGS):]
2001
2002 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05002003 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002004 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002005 if is_sha1 and not depth:
2006 # When syncing a specific commit and --depth is not set:
2007 # * if upstream is explicitly specified and is not a sha1, fetch only
2008 # upstream as users expect only upstream to be fetch.
2009 # Note: The commit might not be in upstream in which case the sync
2010 # will fail.
2011 # * otherwise, fetch all branches to make sure we end up with the
2012 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002013 if self.upstream:
2014 current_branch_only = not ID_RE.match(self.upstream)
2015 else:
2016 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002017
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002018 if not name:
2019 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002020
2021 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002022 remote = self.GetRemote(name)
2023 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002024 ssh_proxy = True
2025
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002027 if alt_dir and 'objects' == os.path.basename(alt_dir):
2028 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002029 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2030 remote = self.GetRemote(name)
2031
David Pursehouse8a68ff92012-09-24 12:15:13 +09002032 all_refs = self.bare_ref.all
2033 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002034 tmp = set()
2035
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302036 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002037 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002038 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002039 all_refs[r] = ref_id
2040 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002041 continue
2042
David Pursehouse8a68ff92012-09-24 12:15:13 +09002043 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002044 continue
2045
David Pursehouse8a68ff92012-09-24 12:15:13 +09002046 r = 'refs/_alt/%s' % ref_id
2047 all_refs[r] = ref_id
2048 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002049 tmp.add(r)
2050
Shawn O. Pearce88443382010-10-08 10:02:09 +02002051 tmp_packed = ''
2052 old_packed = ''
2053
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302054 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002055 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056 tmp_packed += line
2057 if r not in tmp:
2058 old_packed += line
2059
2060 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002061 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002062 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002063
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002064 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002065
Conley Owensf97e8382015-01-21 11:12:46 -08002066 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002067 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002068 else:
2069 # If this repo has shallow objects, then we don't know which refs have
2070 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2071 # do this with projects that don't have shallow objects, since it is less
2072 # efficient.
2073 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2074 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002075
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002076 if quiet:
2077 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002078 if not self.worktree:
2079 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002080 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002081
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002082 # If using depth then we should not get all the tags since they may
2083 # be outside of the depth.
2084 if no_tags or depth:
2085 cmd.append('--no-tags')
2086 else:
2087 cmd.append('--tags')
2088
David Pursehouse74cfd272015-10-14 10:50:15 +09002089 if prune:
2090 cmd.append('--prune')
2091
Conley Owens80b87fe2014-05-09 17:13:44 -07002092 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002093 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002094 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002095 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002096 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002097 spec.append('tag')
2098 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002099
David Pursehouse403b64e2015-04-27 10:41:33 +09002100 if not self.manifest.IsMirror:
2101 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002102 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002103 # Shallow checkout of a specific commit, fetch from that commit and not
2104 # the heads only as the commit might be deeper in the history.
2105 spec.append(branch)
2106 else:
2107 if is_sha1:
2108 branch = self.upstream
2109 if branch is not None and branch.strip():
2110 if not branch.startswith('refs/'):
2111 branch = R_HEADS + branch
2112 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002113 cmd.extend(spec)
2114
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002115 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002116 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002117 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002118 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002119 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002120 ok = True
2121 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002122 # If needed, run the 'git remote prune' the first time through the loop
2123 elif (not _i and
2124 "error:" in gitcmd.stderr and
2125 "git remote prune" in gitcmd.stderr):
2126 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002127 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002128 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002129 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002130 break
2131 continue
Brian Harring14a66742012-09-28 20:21:57 -07002132 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002133 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2134 # in sha1 mode, we just tried sync'ing from the upstream field; it
2135 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002136 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002137 elif ret < 0:
2138 # Git died with a signal, exit immediately
2139 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002140 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002141
2142 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002143 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002144 if old_packed != '':
2145 _lwrite(packed_refs, old_packed)
2146 else:
2147 os.remove(packed_refs)
2148 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002149
2150 if is_sha1 and current_branch_only and self.upstream:
2151 # We just synced the upstream given branch; verify we
2152 # got what we wanted, else trigger a second run of all
2153 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002154 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002155 if not depth:
2156 # Avoid infinite recursion when depth is True (since depth implies
2157 # current_branch_only)
2158 return self._RemoteFetch(name=name, current_branch_only=False,
2159 initial=False, quiet=quiet, alt_dir=alt_dir)
2160 if self.clone_depth:
2161 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002162 return self._RemoteFetch(name=name,
2163 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002164 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002165
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002166 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002167
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002168 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002169 if initial and \
2170 (self.manifest.manifestProject.config.GetString('repo.depth') or
2171 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002172 return False
2173
2174 remote = self.GetRemote(self.remote.name)
2175 bundle_url = remote.url + '/clone.bundle'
2176 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002177 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2178 'persistent-http',
2179 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002180 return False
2181
2182 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2183 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2184
2185 exist_dst = os.path.exists(bundle_dst)
2186 exist_tmp = os.path.exists(bundle_tmp)
2187
2188 if not initial and not exist_dst and not exist_tmp:
2189 return False
2190
2191 if not exist_dst:
2192 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2193 if not exist_dst:
2194 return False
2195
2196 cmd = ['fetch']
2197 if quiet:
2198 cmd.append('--quiet')
2199 if not self.worktree:
2200 cmd.append('--update-head-ok')
2201 cmd.append(bundle_dst)
2202 for f in remote.fetch:
2203 cmd.append(str(f))
2204 cmd.append('refs/tags/*:refs/tags/*')
2205
2206 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002207 if os.path.exists(bundle_dst):
2208 os.remove(bundle_dst)
2209 if os.path.exists(bundle_tmp):
2210 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002211 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002213 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002214 if os.path.exists(dstPath):
2215 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002216
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002217 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002218 if quiet:
2219 cmd += ['--silent']
2220 if os.path.exists(tmpPath):
2221 size = os.stat(tmpPath).st_size
2222 if size >= 1024:
2223 cmd += ['--continue-at', '%d' % (size,)]
2224 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002225 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002226 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2227 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002228 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002229 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002230 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002231 if srcUrl.startswith('persistent-'):
2232 srcUrl = srcUrl[len('persistent-'):]
2233 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002234
Dave Borowitz137d0132015-01-02 11:12:54 -08002235 if IsTrace():
2236 Trace('%s', ' '.join(cmd))
2237 try:
2238 proc = subprocess.Popen(cmd)
2239 except OSError:
2240 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002241
Dave Borowitz137d0132015-01-02 11:12:54 -08002242 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002243
Dave Borowitz137d0132015-01-02 11:12:54 -08002244 if curlret == 22:
2245 # From curl man page:
2246 # 22: HTTP page not retrieved. The requested url was not found or
2247 # returned another error with the HTTP error code being 400 or above.
2248 # This return code only appears if -f, --fail is used.
2249 if not quiet:
2250 print("Server does not provide clone.bundle; ignoring.",
2251 file=sys.stderr)
2252 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002253
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002254 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002255 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002256 os.rename(tmpPath, dstPath)
2257 return True
2258 else:
2259 os.remove(tmpPath)
2260 return False
2261 else:
2262 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002263
Kris Giesingc8d882a2014-12-23 13:02:32 -08002264 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002265 try:
2266 with open(path) as f:
2267 if f.read(16) == '# v2 git bundle\n':
2268 return True
2269 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002270 if not quiet:
2271 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002272 return False
2273 except OSError:
2274 return False
2275
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002276 def _Checkout(self, rev, quiet=False):
2277 cmd = ['checkout']
2278 if quiet:
2279 cmd.append('-q')
2280 cmd.append(rev)
2281 cmd.append('--')
2282 if GitCommand(self, cmd).Wait() != 0:
2283 if self._allrefs:
2284 raise GitError('%s checkout %s ' % (self.name, rev))
2285
Anthony King7bdac712014-07-16 12:56:40 +01002286 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002287 cmd = ['cherry-pick']
2288 cmd.append(rev)
2289 cmd.append('--')
2290 if GitCommand(self, cmd).Wait() != 0:
2291 if self._allrefs:
2292 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2293
Anthony King7bdac712014-07-16 12:56:40 +01002294 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002295 cmd = ['revert']
2296 cmd.append('--no-edit')
2297 cmd.append(rev)
2298 cmd.append('--')
2299 if GitCommand(self, cmd).Wait() != 0:
2300 if self._allrefs:
2301 raise GitError('%s revert %s ' % (self.name, rev))
2302
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002303 def _ResetHard(self, rev, quiet=True):
2304 cmd = ['reset', '--hard']
2305 if quiet:
2306 cmd.append('-q')
2307 cmd.append(rev)
2308 if GitCommand(self, cmd).Wait() != 0:
2309 raise GitError('%s reset --hard %s ' % (self.name, rev))
2310
Anthony King7bdac712014-07-16 12:56:40 +01002311 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002312 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313 if onto is not None:
2314 cmd.extend(['--onto', onto])
2315 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002316 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317 raise GitError('%s rebase %s ' % (self.name, upstream))
2318
Pierre Tardy3d125942012-05-04 12:18:12 +02002319 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002320 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002321 if ffonly:
2322 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002323 if GitCommand(self, cmd).Wait() != 0:
2324 raise GitError('%s merge %s ' % (self.name, head))
2325
Kevin Degiabaa7f32014-11-12 11:27:45 -07002326 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002327 init_git_dir = not os.path.exists(self.gitdir)
2328 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002329 try:
2330 # Initialize the bare repository, which contains all of the objects.
2331 if init_obj_dir:
2332 os.makedirs(self.objdir)
2333 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002334
Kevin Degib1a07b82015-07-27 13:33:43 -06002335 # If we have a separate directory to hold refs, initialize it as well.
2336 if self.objdir != self.gitdir:
2337 if init_git_dir:
2338 os.makedirs(self.gitdir)
2339
2340 if init_obj_dir or init_git_dir:
2341 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2342 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002343 try:
2344 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2345 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002346 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002347 print("Retrying clone after deleting %s" %
2348 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002349 try:
2350 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002351 if self.worktree and os.path.exists(os.path.realpath
2352 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002353 shutil.rmtree(os.path.realpath(self.worktree))
2354 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2355 except:
2356 raise e
2357 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002358
Kevin Degi384b3c52014-10-16 16:02:58 -06002359 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002360 mp = self.manifest.manifestProject
2361 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002362
Kevin Degib1a07b82015-07-27 13:33:43 -06002363 if ref_dir or mirror_git:
2364 if not mirror_git:
2365 mirror_git = os.path.join(ref_dir, self.name + '.git')
2366 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2367 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002368
Kevin Degib1a07b82015-07-27 13:33:43 -06002369 if os.path.exists(mirror_git):
2370 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002371
Kevin Degib1a07b82015-07-27 13:33:43 -06002372 elif os.path.exists(repo_git):
2373 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002374
Kevin Degib1a07b82015-07-27 13:33:43 -06002375 else:
2376 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002377
Kevin Degib1a07b82015-07-27 13:33:43 -06002378 if ref_dir:
2379 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2380 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002381
Kevin Degib1a07b82015-07-27 13:33:43 -06002382 self._UpdateHooks()
2383
2384 m = self.manifest.manifestProject.config
2385 for key in ['user.name', 'user.email']:
2386 if m.Has(key, include_defaults=False):
2387 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002388 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002389 if self.manifest.IsMirror:
2390 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002391 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002392 self.config.SetString('core.bare', None)
2393 except Exception:
2394 if init_obj_dir and os.path.exists(self.objdir):
2395 shutil.rmtree(self.objdir)
2396 if init_git_dir and os.path.exists(self.gitdir):
2397 shutil.rmtree(self.gitdir)
2398 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002399
Jimmie Westera0444582012-10-24 13:44:42 +02002400 def _UpdateHooks(self):
2401 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002402 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002404 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002405 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002406 if not os.path.exists(hooks):
2407 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002408 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002409 name = os.path.basename(stock_hook)
2410
Victor Boivie65e0f352011-04-18 11:23:29 +02002411 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002412 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002413 # Don't install a Gerrit Code Review hook if this
2414 # project does not appear to use it for reviews.
2415 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002416 # Since the manifest project is one of those, but also
2417 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002418 continue
2419
2420 dst = os.path.join(hooks, name)
2421 if os.path.islink(dst):
2422 continue
2423 if os.path.exists(dst):
2424 if filecmp.cmp(stock_hook, dst, shallow=False):
2425 os.remove(dst)
2426 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002427 _warn("%s: Not replacing locally modified %s hook",
2428 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002429 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002430 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002431 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002432 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002433 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002434 raise GitError('filesystem must support symlinks')
2435 else:
2436 raise
2437
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002438 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002439 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002440 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002441 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002442 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002443 remote.review = self.remote.review
2444 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002445
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002446 if self.worktree:
2447 remote.ResetFetch(mirror=False)
2448 else:
2449 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450 remote.Save()
2451
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002452 def _InitMRef(self):
2453 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002454 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002455
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002456 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002457 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002458
2459 def _InitAnyMRef(self, ref):
2460 cur = self.bare_ref.symref(ref)
2461
2462 if self.revisionId:
2463 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2464 msg = 'manifest set to %s' % self.revisionId
2465 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002466 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002467 else:
2468 remote = self.GetRemote(self.remote.name)
2469 dst = remote.ToLocal(self.revisionExpr)
2470 if cur != dst:
2471 msg = 'manifest set to %s' % self.revisionExpr
2472 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002473
Kevin Degi384b3c52014-10-16 16:02:58 -06002474 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002475 symlink_files = self.shareable_files[:]
2476 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002477 if share_refs:
2478 symlink_files += self.working_tree_files
2479 symlink_dirs += self.working_tree_dirs
2480 to_symlink = symlink_files + symlink_dirs
2481 for name in set(to_symlink):
2482 dst = os.path.realpath(os.path.join(destdir, name))
2483 if os.path.lexists(dst):
2484 src = os.path.realpath(os.path.join(srcdir, name))
2485 # Fail if the links are pointing to the wrong place
2486 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002487 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002488 'work tree. If you\'re comfortable with the '
2489 'possibility of losing the work tree\'s git metadata,'
2490 ' use `repo sync --force-sync {0}` to '
2491 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002492
David James8d201162013-10-11 17:03:19 -07002493 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2494 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2495
2496 Args:
2497 gitdir: The bare git repository. Must already be initialized.
2498 dotgit: The repository you would like to initialize.
2499 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2500 Only one work tree can store refs under a given |gitdir|.
2501 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2502 This saves you the effort of initializing |dotgit| yourself.
2503 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002504 symlink_files = self.shareable_files[:]
2505 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002506 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002507 symlink_files += self.working_tree_files
2508 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002509 to_symlink = symlink_files + symlink_dirs
2510
2511 to_copy = []
2512 if copy_all:
2513 to_copy = os.listdir(gitdir)
2514
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002515 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002516 for name in set(to_copy).union(to_symlink):
2517 try:
2518 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002519 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002520
Kevin Degi384b3c52014-10-16 16:02:58 -06002521 if os.path.lexists(dst):
2522 continue
David James8d201162013-10-11 17:03:19 -07002523
2524 # If the source dir doesn't exist, create an empty dir.
2525 if name in symlink_dirs and not os.path.lexists(src):
2526 os.makedirs(src)
2527
Conley Owens80b87fe2014-05-09 17:13:44 -07002528 # If the source file doesn't exist, ensure the destination
2529 # file doesn't either.
2530 if name in symlink_files and not os.path.lexists(src):
2531 try:
2532 os.remove(dst)
2533 except OSError:
2534 pass
2535
David James8d201162013-10-11 17:03:19 -07002536 if name in to_symlink:
2537 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2538 elif copy_all and not os.path.islink(dst):
2539 if os.path.isdir(src):
2540 shutil.copytree(src, dst)
2541 elif os.path.isfile(src):
2542 shutil.copy(src, dst)
2543 except OSError as e:
2544 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002545 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002546 else:
2547 raise
2548
Kevin Degiabaa7f32014-11-12 11:27:45 -07002549 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002550 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002551 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002552 try:
2553 if init_dotgit:
2554 os.makedirs(dotgit)
2555 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2556 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002557
Kevin Degiabaa7f32014-11-12 11:27:45 -07002558 try:
2559 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2560 except GitError as e:
2561 if force_sync:
2562 try:
2563 shutil.rmtree(dotgit)
2564 return self._InitWorkTree(force_sync=False)
2565 except:
2566 raise e
2567 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002568
Kevin Degib1a07b82015-07-27 13:33:43 -06002569 if init_dotgit:
2570 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002571
Kevin Degib1a07b82015-07-27 13:33:43 -06002572 cmd = ['read-tree', '--reset', '-u']
2573 cmd.append('-v')
2574 cmd.append(HEAD)
2575 if GitCommand(self, cmd).Wait() != 0:
2576 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002577
Kevin Degib1a07b82015-07-27 13:33:43 -06002578 self._CopyAndLinkFiles()
2579 except Exception:
2580 if init_dotgit:
2581 shutil.rmtree(dotgit)
2582 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583
2584 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002585 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002586
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002587 def _revlist(self, *args, **kw):
2588 a = []
2589 a.extend(args)
2590 a.append('--')
2591 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002592
2593 @property
2594 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002595 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002596
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002597 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002598 """Get logs between two revisions of this project."""
2599 comp = '..'
2600 if rev1:
2601 revs = [rev1]
2602 if rev2:
2603 revs.extend([comp, rev2])
2604 cmd = ['log', ''.join(revs)]
2605 out = DiffColoring(self.config)
2606 if out.is_on and color:
2607 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002608 if pretty_format is not None:
2609 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002610 if oneline:
2611 cmd.append('--oneline')
2612
2613 try:
2614 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2615 if log.Wait() == 0:
2616 return log.stdout
2617 except GitError:
2618 # worktree may not exist if groups changed for example. In that case,
2619 # try in gitdir instead.
2620 if not os.path.exists(self.worktree):
2621 return self.bare_git.log(*cmd[1:])
2622 else:
2623 raise
2624 return None
2625
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002626 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2627 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002628 """Get the list of logs from this revision to given revisionId"""
2629 logs = {}
2630 selfId = self.GetRevisionId(self._allrefs)
2631 toId = toProject.GetRevisionId(toProject._allrefs)
2632
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002633 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2634 pretty_format=pretty_format)
2635 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2636 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002637 return logs
2638
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002639 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002640
David James8d201162013-10-11 17:03:19 -07002641 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 self._project = project
2643 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002644 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002646 def LsOthers(self):
2647 p = GitCommand(self._project,
2648 ['ls-files',
2649 '-z',
2650 '--others',
2651 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002652 bare=False,
David James8d201162013-10-11 17:03:19 -07002653 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002654 capture_stdout=True,
2655 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 if p.Wait() == 0:
2657 out = p.stdout
2658 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002659 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002660 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 return []
2662
2663 def DiffZ(self, name, *args):
2664 cmd = [name]
2665 cmd.append('-z')
2666 cmd.extend(args)
2667 p = GitCommand(self._project,
2668 cmd,
David James8d201162013-10-11 17:03:19 -07002669 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002670 bare=False,
2671 capture_stdout=True,
2672 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673 try:
2674 out = p.process.stdout.read()
2675 r = {}
2676 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002677 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002679 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002680 info = next(out)
2681 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002682 except StopIteration:
2683 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002684
2685 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002686
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002687 def __init__(self, path, omode, nmode, oid, nid, state):
2688 self.path = path
2689 self.src_path = None
2690 self.old_mode = omode
2691 self.new_mode = nmode
2692 self.old_id = oid
2693 self.new_id = nid
2694
2695 if len(state) == 1:
2696 self.status = state
2697 self.level = None
2698 else:
2699 self.status = state[:1]
2700 self.level = state[1:]
2701 while self.level.startswith('0'):
2702 self.level = self.level[1:]
2703
2704 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002705 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002706 if info.status in ('R', 'C'):
2707 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002708 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002709 r[info.path] = info
2710 return r
2711 finally:
2712 p.Wait()
2713
2714 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002715 if self._bare:
2716 path = os.path.join(self._project.gitdir, HEAD)
2717 else:
2718 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002719 try:
2720 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002721 except IOError as e:
2722 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002723 try:
2724 line = fd.read()
2725 finally:
2726 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302727 try:
2728 line = line.decode()
2729 except AttributeError:
2730 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002731 if line.startswith('ref: '):
2732 return line[5:-1]
2733 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002734
2735 def SetHead(self, ref, message=None):
2736 cmdv = []
2737 if message is not None:
2738 cmdv.extend(['-m', message])
2739 cmdv.append(HEAD)
2740 cmdv.append(ref)
2741 self.symbolic_ref(*cmdv)
2742
2743 def DetachHead(self, new, message=None):
2744 cmdv = ['--no-deref']
2745 if message is not None:
2746 cmdv.extend(['-m', message])
2747 cmdv.append(HEAD)
2748 cmdv.append(new)
2749 self.update_ref(*cmdv)
2750
2751 def UpdateRef(self, name, new, old=None,
2752 message=None,
2753 detach=False):
2754 cmdv = []
2755 if message is not None:
2756 cmdv.extend(['-m', message])
2757 if detach:
2758 cmdv.append('--no-deref')
2759 cmdv.append(name)
2760 cmdv.append(new)
2761 if old is not None:
2762 cmdv.append(old)
2763 self.update_ref(*cmdv)
2764
2765 def DeleteRef(self, name, old=None):
2766 if not old:
2767 old = self.rev_parse(name)
2768 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002769 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002770
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002771 def rev_list(self, *args, **kw):
2772 if 'format' in kw:
2773 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2774 else:
2775 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002776 cmdv.extend(args)
2777 p = GitCommand(self._project,
2778 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002779 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002780 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002781 capture_stdout=True,
2782 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002783 r = []
2784 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002785 if line[-1] == '\n':
2786 line = line[:-1]
2787 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002789 raise GitError('%s rev-list %s: %s' %
2790 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002791 return r
2792
2793 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002794 """Allow arbitrary git commands using pythonic syntax.
2795
2796 This allows you to do things like:
2797 git_obj.rev_parse('HEAD')
2798
2799 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2800 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002801 Any other positional arguments will be passed to the git command, and the
2802 following keyword arguments are supported:
2803 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002804
2805 Args:
2806 name: The name of the git command to call. Any '_' characters will
2807 be replaced with '-'.
2808
2809 Returns:
2810 A callable object that will try to call git with the named command.
2811 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002813
Dave Borowitz091f8932012-10-23 17:01:04 -07002814 def runner(*args, **kwargs):
2815 cmdv = []
2816 config = kwargs.pop('config', None)
2817 for k in kwargs:
2818 raise TypeError('%s() got an unexpected keyword argument %r'
2819 % (name, k))
2820 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002821 if not git_require((1, 7, 2)):
2822 raise ValueError('cannot set config on command line for %s()'
2823 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302824 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002825 cmdv.append('-c')
2826 cmdv.append('%s=%s' % (k, v))
2827 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002828 cmdv.extend(args)
2829 p = GitCommand(self._project,
2830 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002831 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002832 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002833 capture_stdout=True,
2834 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002835 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002836 raise GitError('%s %s: %s' %
2837 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002838 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302839 try:
Conley Owensedd01512013-09-26 12:59:58 -07002840 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302841 except AttributeError:
2842 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002843 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2844 return r[:-1]
2845 return r
2846 return runner
2847
2848
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002849class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002850
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002851 def __str__(self):
2852 return 'prior sync failed; rebase still in progress'
2853
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002854
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002855class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002856
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002857 def __str__(self):
2858 return 'contains uncommitted changes'
2859
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002860
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002861class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002862
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002863 def __init__(self, project, text):
2864 self.project = project
2865 self.text = text
2866
2867 def Print(self, syncbuf):
2868 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2869 syncbuf.out.nl()
2870
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002871
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002872class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002873
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002874 def __init__(self, project, why):
2875 self.project = project
2876 self.why = why
2877
2878 def Print(self, syncbuf):
2879 syncbuf.out.fail('error: %s/: %s',
2880 self.project.relpath,
2881 str(self.why))
2882 syncbuf.out.nl()
2883
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002884
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002885class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002886
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002887 def __init__(self, project, action):
2888 self.project = project
2889 self.action = action
2890
2891 def Run(self, syncbuf):
2892 out = syncbuf.out
2893 out.project('project %s/', self.project.relpath)
2894 out.nl()
2895 try:
2896 self.action()
2897 out.nl()
2898 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002899 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002900 out.nl()
2901 return False
2902
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002903
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002904class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002905
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002906 def __init__(self, config):
2907 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002908 self.project = self.printer('header', attr='bold')
2909 self.info = self.printer('info')
2910 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002911
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002912
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002913class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002914
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002915 def __init__(self, config, detach_head=False):
2916 self._messages = []
2917 self._failures = []
2918 self._later_queue1 = []
2919 self._later_queue2 = []
2920
2921 self.out = _SyncColoring(config)
2922 self.out.redirect(sys.stderr)
2923
2924 self.detach_head = detach_head
2925 self.clean = True
2926
2927 def info(self, project, fmt, *args):
2928 self._messages.append(_InfoMessage(project, fmt % args))
2929
2930 def fail(self, project, err=None):
2931 self._failures.append(_Failure(project, err))
2932 self.clean = False
2933
2934 def later1(self, project, what):
2935 self._later_queue1.append(_Later(project, what))
2936
2937 def later2(self, project, what):
2938 self._later_queue2.append(_Later(project, what))
2939
2940 def Finish(self):
2941 self._PrintMessages()
2942 self._RunLater()
2943 self._PrintMessages()
2944 return self.clean
2945
2946 def _RunLater(self):
2947 for q in ['_later_queue1', '_later_queue2']:
2948 if not self._RunQueue(q):
2949 return
2950
2951 def _RunQueue(self, queue):
2952 for m in getattr(self, queue):
2953 if not m.Run(self):
2954 self.clean = False
2955 return False
2956 setattr(self, queue, [])
2957 return True
2958
2959 def _PrintMessages(self):
2960 for m in self._messages:
2961 m.Print(self)
2962 for m in self._failures:
2963 m.Print(self)
2964
2965 self._messages = []
2966 self._failures = []
2967
2968
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002969class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002970
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971 """A special project housed under .repo.
2972 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002973
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002976 manifest=manifest,
2977 name=name,
2978 gitdir=gitdir,
2979 objdir=gitdir,
2980 worktree=worktree,
2981 remote=RemoteSpec('origin'),
2982 relpath='.repo/%s' % name,
2983 revisionExpr='refs/heads/master',
2984 revisionId=None,
2985 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986
2987 def PreSync(self):
2988 if self.Exists:
2989 cb = self.CurrentBranch
2990 if cb:
2991 base = self.GetBranch(cb).merge
2992 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002993 self.revisionExpr = base
2994 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995
Anthony King7bdac712014-07-16 12:56:40 +01002996 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002997 """ Prepare MetaProject for manifest branch switch
2998 """
2999
3000 # detach and delete manifest branch, allowing a new
3001 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003002 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02003003 self.Sync_LocalHalf(syncbuf)
3004 syncbuf.Finish()
3005
3006 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003007 ['update-ref', '-d', 'refs/heads/default'],
3008 capture_stdout=True,
3009 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003010
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003011 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003012 def LastFetch(self):
3013 try:
3014 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3015 return os.path.getmtime(fh)
3016 except OSError:
3017 return 0
3018
3019 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003020 def HasChanges(self):
3021 """Has the remote received new commits not yet checked out?
3022 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003023 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003024 return False
3025
David Pursehouse8a68ff92012-09-24 12:15:13 +09003026 all_refs = self.bare_ref.all
3027 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003028 head = self.work_git.GetHead()
3029 if head.startswith(R_HEADS):
3030 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003031 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003032 except KeyError:
3033 head = None
3034
3035 if revid == head:
3036 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003037 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003038 return True
3039 return False