blob: a54c0fbe054b30b6116e2cdd7c0517500112c5cd [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,
Vadim Bendebury329651b2018-10-31 13:48:01 -0700181 notify=None,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200182 wip=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700183 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800184 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700185 people,
Brian Harring435370c2012-07-28 15:37:04 -0700186 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400187 draft=draft,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200188 private=private,
Vadim Bendebury329651b2018-10-31 13:48:01 -0700189 notify=notify,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200190 wip=wip,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400191 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700193 def GetPublishedRefs(self):
194 refs = {}
195 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196 self.branch.remote.SshReviewUrl(self.project.UserEmail),
197 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700198 for line in output.split('\n'):
199 try:
200 (sha, ref) = line.split()
201 refs[sha] = ref
202 except ValueError:
203 pass
204
205 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700209
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 def __init__(self, config):
211 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100212 self.project = self.printer('header', attr='bold')
213 self.branch = self.printer('header', attr='bold')
214 self.nobranch = self.printer('nobranch', fg='red')
215 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Anthony King7bdac712014-07-16 12:56:40 +0100217 self.added = self.printer('added', fg='green')
218 self.changed = self.printer('changed', fg='red')
219 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
221
222class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 def __init__(self, config):
225 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100226 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228
Anthony King7bdac712014-07-16 12:56:40 +0100229class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700230
James W. Mills24c13082012-04-12 15:04:13 -0500231 def __init__(self, name, value, keep):
232 self.name = name
233 self.value = value
234 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
Anthony King7bdac712014-07-16 12:56:40 +0100237class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 self.src = src
241 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800242 self.abs_src = abssrc
243 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800246 src = self.abs_src
247 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 # copy file if it does not exist or is out of date
249 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
250 try:
251 # remove existing file first, since it might be read-only
252 if os.path.exists(dest):
253 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400254 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200255 dest_dir = os.path.dirname(dest)
256 if not os.path.isdir(dest_dir):
257 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258 shutil.copy(src, dest)
259 # make the file read-only
260 mode = os.stat(dest)[stat.ST_MODE]
261 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
262 os.chmod(dest, mode)
263 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700264 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Anthony King7bdac712014-07-16 12:56:40 +0100267class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 def __init__(self, git_worktree, src, dest, relsrc, absdest):
270 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 self.src = src
272 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700273 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500274 self.abs_dest = absdest
275
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700278 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500279 try:
280 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800281 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500283 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700284 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500285 if not os.path.isdir(dest_dir):
286 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700287 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500288 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700289 _error('Cannot link file %s to %s', relSrc, absDest)
290
291 def _Link(self):
292 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
293 on the src linking all of the files in the source in to the destination
294 directory.
295 """
296 # We use the absSrc to handle the situation where the current directory
297 # is not the root of the repo
298 absSrc = os.path.join(self.git_worktree, self.src)
299 if os.path.exists(absSrc):
300 # Entity exists so just a simple one to one link operation
301 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
302 else:
303 # Entity doesn't exist assume there is a wild card
304 absDestDir = self.abs_dest
305 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
306 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700307 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700308 else:
309 absSrcFiles = glob.glob(absSrc)
310 for absSrcFile in absSrcFiles:
311 # Create a releative path from source dir to destination dir
312 absSrcDir = os.path.dirname(absSrcFile)
313 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
314
315 # Get the source file name
316 srcFile = os.path.basename(absSrcFile)
317
318 # Now form the final full paths to srcFile. They will be
319 # absolute for the desintaiton and relative for the srouce.
320 absDest = os.path.join(absDestDir, srcFile)
321 relSrc = os.path.join(relSrcDir, srcFile)
322 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500323
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700325class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700326
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700327 def __init__(self,
328 name,
Anthony King7bdac712014-07-16 12:56:40 +0100329 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700330 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100331 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700332 revision=None,
333 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700334 self.name = name
335 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700336 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700337 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100338 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700339 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700341
Doug Anderson37282b42011-03-04 11:54:18 -0800342class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700343
Doug Anderson37282b42011-03-04 11:54:18 -0800344 """A RepoHook contains information about a script to run as a hook.
345
346 Hooks are used to run a python script before running an upload (for instance,
347 to run presubmit checks). Eventually, we may have hooks for other actions.
348
349 This shouldn't be confused with files in the 'repo/hooks' directory. Those
350 files are copied into each '.git/hooks' folder for each project. Repo-level
351 hooks are associated instead with repo actions.
352
353 Hooks are always python. When a hook is run, we will load the hook into the
354 interpreter and execute its main() function.
355 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700356
Doug Anderson37282b42011-03-04 11:54:18 -0800357 def __init__(self,
358 hook_type,
359 hooks_project,
360 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400361 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800362 abort_if_user_denies=False):
363 """RepoHook constructor.
364
365 Params:
366 hook_type: A string representing the type of hook. This is also used
367 to figure out the name of the file containing the hook. For
368 example: 'pre-upload'.
369 hooks_project: The project containing the repo hooks. If you have a
370 manifest, this is manifest.repo_hooks_project. OK if this is None,
371 which will make the hook a no-op.
372 topdir: Repo's top directory (the one containing the .repo directory).
373 Scripts will run with CWD as this directory. If you have a manifest,
374 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400375 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800376 abort_if_user_denies: If True, we'll throw a HookError() if the user
377 doesn't allow us to run the hook.
378 """
379 self._hook_type = hook_type
380 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400381 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800382 self._topdir = topdir
383 self._abort_if_user_denies = abort_if_user_denies
384
385 # Store the full path to the script for convenience.
386 if self._hooks_project:
387 self._script_fullpath = os.path.join(self._hooks_project.worktree,
388 self._hook_type + '.py')
389 else:
390 self._script_fullpath = None
391
392 def _GetHash(self):
393 """Return a hash of the contents of the hooks directory.
394
395 We'll just use git to do this. This hash has the property that if anything
396 changes in the directory we will return a different has.
397
398 SECURITY CONSIDERATION:
399 This hash only represents the contents of files in the hook directory, not
400 any other files imported or called by hooks. Changes to imported files
401 can change the script behavior without affecting the hash.
402
403 Returns:
404 A string representing the hash. This will always be ASCII so that it can
405 be printed to the user easily.
406 """
407 assert self._hooks_project, "Must have hooks to calculate their hash."
408
409 # We will use the work_git object rather than just calling GetRevisionId().
410 # That gives us a hash of the latest checked in version of the files that
411 # the user will actually be executing. Specifically, GetRevisionId()
412 # doesn't appear to change even if a user checks out a different version
413 # of the hooks repo (via git checkout) nor if a user commits their own revs.
414 #
415 # NOTE: Local (non-committed) changes will not be factored into this hash.
416 # I think this is OK, since we're really only worried about warning the user
417 # about upstream changes.
418 return self._hooks_project.work_git.rev_parse('HEAD')
419
420 def _GetMustVerb(self):
421 """Return 'must' if the hook is required; 'should' if not."""
422 if self._abort_if_user_denies:
423 return 'must'
424 else:
425 return 'should'
426
427 def _CheckForHookApproval(self):
428 """Check to see whether this hook has been approved.
429
Mike Frysinger40252c22016-08-15 21:23:44 -0400430 We'll accept approval of manifest URLs if they're using secure transports.
431 This way the user can say they trust the manifest hoster. For insecure
432 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800433
434 Note that we ask permission for each individual hook even though we use
435 the hash of all hooks when detecting changes. We'd like the user to be
436 able to approve / deny each hook individually. We only use the hash of all
437 hooks because there is no other easy way to detect changes to local imports.
438
439 Returns:
440 True if this hook is approved to run; False otherwise.
441
442 Raises:
443 HookError: Raised if the user doesn't approve and abort_if_user_denies
444 was passed to the consturctor.
445 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400446 if self._ManifestUrlHasSecureScheme():
447 return self._CheckForHookApprovalManifest()
448 else:
449 return self._CheckForHookApprovalHash()
450
451 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
452 changed_prompt):
453 """Check for approval for a particular attribute and hook.
454
455 Args:
456 subkey: The git config key under [repo.hooks.<hook_type>] to store the
457 last approved string.
458 new_val: The new value to compare against the last approved one.
459 main_prompt: Message to display to the user to ask for approval.
460 changed_prompt: Message explaining why we're re-asking for approval.
461
462 Returns:
463 True if this hook is approved to run; False otherwise.
464
465 Raises:
466 HookError: Raised if the user doesn't approve and abort_if_user_denies
467 was passed to the consturctor.
468 """
Doug Anderson37282b42011-03-04 11:54:18 -0800469 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800471
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 # Get the last value that the user approved for this hook; may be None.
473 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800474
Mike Frysinger40252c22016-08-15 21:23:44 -0400475 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800476 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800478 # Approval matched. We're done.
479 return True
480 else:
481 # Give the user a reason why we're prompting, since they last told
482 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800484 else:
485 prompt = ''
486
487 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
488 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400489 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530490 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900491 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800492
493 # User is doing a one-time approval.
494 if response in ('y', 'yes'):
495 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400496 elif response == 'always':
497 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800498 return True
499
500 # For anything else, we'll assume no approval.
501 if self._abort_if_user_denies:
502 raise HookError('You must allow the %s hook or use --no-verify.' %
503 self._hook_type)
504
505 return False
506
Mike Frysinger40252c22016-08-15 21:23:44 -0400507 def _ManifestUrlHasSecureScheme(self):
508 """Check if the URI for the manifest is a secure transport."""
509 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
510 parse_results = urllib.parse.urlparse(self._manifest_url)
511 return parse_results.scheme in secure_schemes
512
513 def _CheckForHookApprovalManifest(self):
514 """Check whether the user has approved this manifest host.
515
516 Returns:
517 True if this hook is approved to run; False otherwise.
518 """
519 return self._CheckForHookApprovalHelper(
520 'approvedmanifest',
521 self._manifest_url,
522 'Run hook scripts from %s' % (self._manifest_url,),
523 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
524
525 def _CheckForHookApprovalHash(self):
526 """Check whether the user has approved the hooks repo.
527
528 Returns:
529 True if this hook is approved to run; False otherwise.
530 """
531 prompt = ('Repo %s run the script:\n'
532 ' %s\n'
533 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700534 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400535 return self._CheckForHookApprovalHelper(
536 'approvedhash',
537 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700538 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400539 'Scripts have changed since %s was allowed.' % (self._hook_type,))
540
Doug Anderson37282b42011-03-04 11:54:18 -0800541 def _ExecuteHook(self, **kwargs):
542 """Actually execute the given hook.
543
544 This will run the hook's 'main' function in our python interpreter.
545
546 Args:
547 kwargs: Keyword arguments to pass to the hook. These are often specific
548 to the hook type. For instance, pre-upload hooks will contain
549 a project_list.
550 """
551 # Keep sys.path and CWD stashed away so that we can always restore them
552 # upon function exit.
553 orig_path = os.getcwd()
554 orig_syspath = sys.path
555
556 try:
557 # Always run hooks with CWD as topdir.
558 os.chdir(self._topdir)
559
560 # Put the hook dir as the first item of sys.path so hooks can do
561 # relative imports. We want to replace the repo dir as [0] so
562 # hooks can't import repo files.
563 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
564
565 # Exec, storing global context in the context dict. We catch exceptions
566 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500567 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800568 try:
Anthony King70f68902014-05-05 21:15:34 +0100569 exec(compile(open(self._script_fullpath).read(),
570 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800571 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700572 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
573 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800574
575 # Running the script should have defined a main() function.
576 if 'main' not in context:
577 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
578
Doug Anderson37282b42011-03-04 11:54:18 -0800579 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
580 # We don't actually want hooks to define their main with this argument--
581 # it's there to remind them that their hook should always take **kwargs.
582 # For instance, a pre-upload hook should be defined like:
583 # def main(project_list, **kwargs):
584 #
585 # This allows us to later expand the API without breaking old hooks.
586 kwargs = kwargs.copy()
587 kwargs['hook_should_take_kwargs'] = True
588
589 # Call the main function in the hook. If the hook should cause the
590 # build to fail, it will raise an Exception. We'll catch that convert
591 # to a HookError w/ just the failing traceback.
592 try:
593 context['main'](**kwargs)
594 except Exception:
595 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700596 'above.' % (traceback.format_exc(),
597 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800598 finally:
599 # Restore sys.path and CWD.
600 sys.path = orig_syspath
601 os.chdir(orig_path)
602
603 def Run(self, user_allows_all_hooks, **kwargs):
604 """Run the hook.
605
606 If the hook doesn't exist (because there is no hooks project or because
607 this particular hook is not enabled), this is a no-op.
608
609 Args:
610 user_allows_all_hooks: If True, we will never prompt about running the
611 hook--we'll just assume it's OK to run it.
612 kwargs: Keyword arguments to pass to the hook. These are often specific
613 to the hook type. For instance, pre-upload hooks will contain
614 a project_list.
615
616 Raises:
617 HookError: If there was a problem finding the hook or the user declined
618 to run a required hook (from _CheckForHookApproval).
619 """
620 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700621 if ((not self._hooks_project) or (self._hook_type not in
622 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800623 return
624
625 # Bail with a nice error if we can't find the hook.
626 if not os.path.isfile(self._script_fullpath):
627 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
628
629 # Make sure the user is OK with running the hook.
630 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
631 return
632
633 # Run the hook with the same version of python we're using.
634 self._ExecuteHook(**kwargs)
635
636
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600638 # These objects can be shared between several working trees.
639 shareable_files = ['description', 'info']
640 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
641 # These objects can only be used by a single working tree.
642 working_tree_files = ['config', 'packed-refs', 'shallow']
643 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 def __init__(self,
646 manifest,
647 name,
648 remote,
649 gitdir,
David James8d201162013-10-11 17:03:19 -0700650 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651 worktree,
652 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700653 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800654 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100655 rebase=True,
656 groups=None,
657 sync_c=False,
658 sync_s=False,
659 clone_depth=None,
660 upstream=None,
661 parent=None,
662 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900663 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700664 optimized_fetch=False,
665 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800666 """Init a Project object.
667
668 Args:
669 manifest: The XmlManifest object.
670 name: The `name` attribute of manifest.xml's project element.
671 remote: RemoteSpec object specifying its remote's properties.
672 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700673 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800674 worktree: Absolute path of git working tree.
675 relpath: Relative path of git working tree to repo's top directory.
676 revisionExpr: The `revision` attribute of manifest.xml's project element.
677 revisionId: git commit id for checking out.
678 rebase: The `rebase` attribute of manifest.xml's project element.
679 groups: The `groups` attribute of manifest.xml's project element.
680 sync_c: The `sync-c` attribute of manifest.xml's project element.
681 sync_s: The `sync-s` attribute of manifest.xml's project element.
682 upstream: The `upstream` attribute of manifest.xml's project element.
683 parent: The parent Project object.
684 is_derived: False if the project was explicitly defined in the manifest;
685 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400686 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900687 optimized_fetch: If True, when a project is set to a sha1 revision, only
688 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700689 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800690 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 self.manifest = manifest
692 self.name = name
693 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800694 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700695 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800696 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700697 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800698 else:
699 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700701 self.revisionExpr = revisionExpr
702
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700703 if revisionId is None \
704 and revisionExpr \
705 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700706 self.revisionId = revisionExpr
707 else:
708 self.revisionId = revisionId
709
Mike Pontillod3153822012-02-28 11:53:24 -0800710 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700711 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700712 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800713 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900714 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700715 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800716 self.parent = parent
717 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900718 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800719 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800720
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500723 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500724 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700725 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
726 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800728 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700729 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800730 else:
731 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700732 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700733 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700734 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400735 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700736 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
Doug Anderson37282b42011-03-04 11:54:18 -0800738 # This will be filled in if a project is later identified to be the
739 # project containing repo hooks.
740 self.enabled_repo_hooks = []
741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800743 def Derived(self):
744 return self.is_derived
745
746 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600748 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749
750 @property
751 def CurrentBranch(self):
752 """Obtain the name of the currently checked out branch.
753 The branch name omits the 'refs/heads/' prefix.
754 None is returned if the project is on a detached HEAD.
755 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700756 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 if b.startswith(R_HEADS):
758 return b[len(R_HEADS):]
759 return None
760
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700761 def IsRebaseInProgress(self):
762 w = self.worktree
763 g = os.path.join(w, '.git')
764 return os.path.exists(os.path.join(g, 'rebase-apply')) \
765 or os.path.exists(os.path.join(g, 'rebase-merge')) \
766 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 def IsDirty(self, consider_untracked=True):
769 """Is the working directory modified in some way?
770 """
771 self.work_git.update_index('-q',
772 '--unmerged',
773 '--ignore-missing',
774 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900775 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 return True
777 if self.work_git.DiffZ('diff-files'):
778 return True
779 if consider_untracked and self.work_git.LsOthers():
780 return True
781 return False
782
783 _userident_name = None
784 _userident_email = None
785
786 @property
787 def UserName(self):
788 """Obtain the user's personal name.
789 """
790 if self._userident_name is None:
791 self._LoadUserIdentity()
792 return self._userident_name
793
794 @property
795 def UserEmail(self):
796 """Obtain the user's email address. This is very likely
797 to be their Gerrit login.
798 """
799 if self._userident_email is None:
800 self._LoadUserIdentity()
801 return self._userident_email
802
803 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900804 u = self.bare_git.var('GIT_COMMITTER_IDENT')
805 m = re.compile("^(.*) <([^>]*)> ").match(u)
806 if m:
807 self._userident_name = m.group(1)
808 self._userident_email = m.group(2)
809 else:
810 self._userident_name = ''
811 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
813 def GetRemote(self, name):
814 """Get the configuration for a single remote.
815 """
816 return self.config.GetRemote(name)
817
818 def GetBranch(self, name):
819 """Get the configuration for a single branch.
820 """
821 return self.config.GetBranch(name)
822
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700823 def GetBranches(self):
824 """Get all existing local branches.
825 """
826 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700828 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700829
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530830 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831 if name.startswith(R_HEADS):
832 name = name[len(R_HEADS):]
833 b = self.GetBranch(name)
834 b.current = name == current
835 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900836 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700837 heads[name] = b
838
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530839 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700840 if name.startswith(R_PUB):
841 name = name[len(R_PUB):]
842 b = heads.get(name)
843 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700845
846 return heads
847
Colin Cross5acde752012-03-28 20:15:45 -0700848 def MatchesGroups(self, manifest_groups):
849 """Returns true if the manifest groups specified at init should cause
850 this project to be synced.
851 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700852 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700853
854 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700855 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700856 manifest_groups: "-group1,group2"
857 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500858
859 The special manifest group "default" will match any project that
860 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700861 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500862 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700863 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700864 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500865 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700866
Conley Owens971de8e2012-04-16 10:36:08 -0700867 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700868 for group in expanded_manifest_groups:
869 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700870 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700871 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700872 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700873
Conley Owens971de8e2012-04-16 10:36:08 -0700874 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700876# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700877 def UncommitedFiles(self, get_all=True):
878 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700880 Args:
881 get_all: a boolean, if True - get information about all different
882 uncommitted files. If False - return as soon as any kind of
883 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500884 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700885 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500886 self.work_git.update_index('-q',
887 '--unmerged',
888 '--ignore-missing',
889 '--refresh')
890 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700891 details.append("rebase in progress")
892 if not get_all:
893 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500894
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700895 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
896 if changes:
897 details.extend(changes)
898 if not get_all:
899 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500900
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700901 changes = self.work_git.DiffZ('diff-files').keys()
902 if changes:
903 details.extend(changes)
904 if not get_all:
905 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500906
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700907 changes = self.work_git.LsOthers()
908 if changes:
909 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500910
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700911 return details
912
913 def HasChanges(self):
914 """Returns true if there are uncommitted changes.
915 """
916 if self.UncommitedFiles(get_all=False):
917 return True
918 else:
919 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500920
Terence Haddock4655e812011-03-31 12:33:34 +0200921 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200923
924 Args:
925 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 """
927 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700928 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200929 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700930 print(file=output_redir)
931 print('project %s/' % self.relpath, file=output_redir)
932 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 return
934
935 self.work_git.update_index('-q',
936 '--unmerged',
937 '--ignore-missing',
938 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700939 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
941 df = self.work_git.DiffZ('diff-files')
942 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100943 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700944 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945
946 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700947 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200948 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700949 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950
951 branch = self.CurrentBranch
952 if branch is None:
953 out.nobranch('(*** NO BRANCH ***)')
954 else:
955 out.branch('branch %s', branch)
956 out.nl()
957
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700958 if rb:
959 out.important('prior sync failed; rebase still in progress')
960 out.nl()
961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 paths = list()
963 paths.extend(di.keys())
964 paths.extend(df.keys())
965 paths.extend(do)
966
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530967 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900968 try:
969 i = di[p]
970 except KeyError:
971 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900973 try:
974 f = df[p]
975 except KeyError:
976 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200977
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900978 if i:
979 i_status = i.status.upper()
980 else:
981 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900983 if f:
984 f_status = f.status.lower()
985 else:
986 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
988 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800989 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700990 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 else:
992 line = ' %s%s\t%s' % (i_status, f_status, p)
993
994 if i and not f:
995 out.added('%s', line)
996 elif (i and f) or (not i and f):
997 out.changed('%s', line)
998 elif not i and not f:
999 out.untracked('%s', line)
1000 else:
1001 out.write('%s', line)
1002 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001003
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001004 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005
pelyad67872d2012-03-28 14:49:58 +03001006 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 """Prints the status of the repository to stdout.
1008 """
1009 out = DiffColoring(self.config)
1010 cmd = ['diff']
1011 if out.is_on:
1012 cmd.append('--color')
1013 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001014 if absolute_paths:
1015 cmd.append('--src-prefix=a/%s/' % self.relpath)
1016 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 cmd.append('--')
1018 p = GitCommand(self,
1019 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001020 capture_stdout=True,
1021 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 has_diff = False
1023 for line in p.process.stdout:
1024 if not has_diff:
1025 out.nl()
1026 out.project('project %s/' % self.relpath)
1027 out.nl()
1028 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001029 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030 p.Wait()
1031
1032
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001033# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
David Pursehouse8a68ff92012-09-24 12:15:13 +09001035 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 """Was the branch published (uploaded) for code review?
1037 If so, returns the SHA-1 hash of the last published
1038 state for the branch.
1039 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001040 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001041 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001042 try:
1043 return self.bare_git.rev_parse(key)
1044 except GitError:
1045 return None
1046 else:
1047 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001049 except KeyError:
1050 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051
David Pursehouse8a68ff92012-09-24 12:15:13 +09001052 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 """Prunes any stale published refs.
1054 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001055 if all_refs is None:
1056 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057 heads = set()
1058 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301059 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 if name.startswith(R_HEADS):
1061 heads.add(name)
1062 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301065 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 n = name[len(R_PUB):]
1067 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001068 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001070 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 """List any branches which can be uploaded for review.
1072 """
1073 heads = {}
1074 pubed = {}
1075
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301076 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001078 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001080 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301083 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001084 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001086 if selected_branch and branch != selected_branch:
1087 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001089 rb = self.GetUploadableBranch(branch)
1090 if rb:
1091 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092 return ready
1093
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001094 def GetUploadableBranch(self, branch_name):
1095 """Get a single uploadable branch, or None.
1096 """
1097 branch = self.GetBranch(branch_name)
1098 base = branch.LocalMerge
1099 if branch.LocalMerge:
1100 rb = ReviewableBranch(self, branch, base)
1101 if rb.commits:
1102 return rb
1103 return None
1104
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001105 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001106 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001107 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001108 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001109 private=False,
Vadim Bendebury329651b2018-10-31 13:48:01 -07001110 notify=None,
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001111 wip=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001112 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113 """Uploads the named branch for code review.
1114 """
1115 if branch is None:
1116 branch = self.CurrentBranch
1117 if branch is None:
1118 raise GitError('not currently on a branch')
1119
1120 branch = self.GetBranch(branch)
1121 if not branch.LocalMerge:
1122 raise GitError('branch %s does not track a remote' % branch.name)
1123 if not branch.remote.review:
1124 raise GitError('remote %s has no review url' % branch.remote.name)
1125
Bryan Jacobsf609f912013-05-06 13:36:24 -04001126 if dest_branch is None:
1127 dest_branch = self.dest_branch
1128 if dest_branch is None:
1129 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130 if not dest_branch.startswith(R_HEADS):
1131 dest_branch = R_HEADS + dest_branch
1132
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001133 if not branch.remote.projectname:
1134 branch.remote.projectname = self.name
1135 branch.remote.Save()
1136
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001137 url = branch.remote.ReviewUrl(self.UserEmail)
1138 if url is None:
1139 raise UploadError('review not configured')
1140 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001141
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001142 if url.startswith('ssh://'):
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001143 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001144
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001145 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001146
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001147 if dest_branch.startswith(R_HEADS):
1148 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001149
1150 upload_type = 'for'
1151 if draft:
1152 upload_type = 'drafts'
1153
1154 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1155 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001156 if auto_topic:
1157 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001158
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001159 opts = ['r=%s' % p for p in people[0]]
1160 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendebury329651b2018-10-31 13:48:01 -07001161 if notify:
1162 opts += ['notify=' + notify]
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001163 if private:
1164 opts += ['private']
1165 if wip:
1166 opts += ['wip']
1167 if opts:
1168 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001169 cmd.append(ref_spec)
1170
Anthony King7bdac712014-07-16 12:56:40 +01001171 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001172 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173
1174 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1175 self.bare_git.UpdateRef(R_PUB + branch.name,
1176 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001177 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178
1179
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001180# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
Julien Campergue335f5ef2013-10-16 11:02:35 +02001182 def _ExtractArchive(self, tarpath, path=None):
1183 """Extract the given tar on its current location
1184
1185 Args:
1186 - tarpath: The path to the actual tar file
1187
1188 """
1189 try:
1190 with tarfile.open(tarpath, 'r') as tar:
1191 tar.extractall(path=path)
1192 return True
1193 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001194 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001195 return False
1196
Ningning Xiac2fbc782016-08-22 14:24:39 -07001197 def CachePopulate(self, cache_dir, url):
1198 """Populate cache in the cache_dir.
1199
1200 Args:
1201 cache_dir: Directory to cache git files from Google Storage.
1202 url: Git url of current repository.
1203
1204 Raises:
1205 CacheApplyError if it fails to populate the git cache.
1206 """
1207 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1208 '--cache-dir', cache_dir, url]
1209
1210 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1211 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1212 'url: %s' % (cache_dir, url))
1213
1214 def CacheExists(self, cache_dir, url):
1215 """Check the existence of the cache files.
1216
1217 Args:
1218 cache_dir: Directory to cache git files.
1219 url: Git url of current repository.
1220
1221 Raises:
1222 CacheApplyError if the cache files do not exist.
1223 """
1224 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1225
1226 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1227 if exist.Wait() != 0:
1228 raise CacheApplyError('Failed to execute git cache exists cmd. '
1229 'cache_dir: %s url: %s' % (cache_dir, url))
1230
1231 if not exist.stdout or not exist.stdout.strip():
1232 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1233 'url: %s' % (cache_dir, url))
1234 return exist.stdout.strip()
1235
1236 def CacheApply(self, cache_dir):
1237 """Apply git cache files populated from Google Storage buckets.
1238
1239 Args:
1240 cache_dir: Directory to cache git files.
1241
1242 Raises:
1243 CacheApplyError if it fails to apply git caches.
1244 """
1245 remote = self.GetRemote(self.remote.name)
1246
1247 self.CachePopulate(cache_dir, remote.url)
1248
1249 mirror_dir = self.CacheExists(cache_dir, remote.url)
1250
1251 refspec = RefSpec(True, 'refs/heads/*',
1252 'refs/remotes/%s/*' % remote.name)
1253
1254 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1255 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1256 raise CacheApplyError('Failed to fetch refs %s from %s' %
1257 (mirror_dir, str(refspec)))
1258
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001259 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001260 quiet=False,
1261 is_new=None,
1262 current_branch_only=False,
1263 force_sync=False,
1264 clone_bundle=True,
1265 no_tags=False,
1266 archive=False,
1267 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001268 prune=False,
1269 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001270 """Perform only the network IO portion of the sync process.
1271 Local working directory/branch state is not affected.
1272 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001273 if archive and not isinstance(self, MetaProject):
1274 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001275 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001276 return False
1277
1278 name = self.relpath.replace('\\', '/')
1279 name = name.replace('/', '_')
1280 tarpath = '%s.tar' % name
1281 topdir = self.manifest.topdir
1282
1283 try:
1284 self._FetchArchive(tarpath, cwd=topdir)
1285 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001286 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001287 return False
1288
1289 # From now on, we only need absolute tarpath
1290 tarpath = os.path.join(topdir, tarpath)
1291
1292 if not self._ExtractArchive(tarpath, path=topdir):
1293 return False
1294 try:
1295 os.remove(tarpath)
1296 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001297 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001298 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001299 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001300 if is_new is None:
1301 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001302 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001303 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001304 else:
1305 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001307
1308 if is_new:
1309 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1310 try:
1311 fd = open(alt, 'rb')
1312 try:
1313 alt_dir = fd.readline().rstrip()
1314 finally:
1315 fd.close()
1316 except IOError:
1317 alt_dir = None
1318 else:
1319 alt_dir = None
1320
Ningning Xiac2fbc782016-08-22 14:24:39 -07001321 applied_cache = False
1322 # If cache_dir is provided, and it's a new repository without
1323 # alternative_dir, bootstrap this project repo with the git
1324 # cache files.
1325 if cache_dir is not None and is_new and alt_dir is None:
1326 try:
1327 self.CacheApply(cache_dir)
1328 applied_cache = True
1329 is_new = False
1330 except CacheApplyError as e:
1331 _error('Could not apply git cache: %s', e)
1332 _error('Please check if you have the right GS credentials.')
1333 _error('Please check if the cache files exist in GS.')
1334
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001335 if clone_bundle \
Ningning Xiac2fbc782016-08-22 14:24:39 -07001336 and not applied_cache \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001337 and alt_dir is None \
1338 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001339 is_new = False
1340
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001341 if not current_branch_only:
1342 if self.sync_c:
1343 current_branch_only = True
1344 elif not self.manifest._loaded:
1345 # Manifest cannot check defaults until it syncs.
1346 current_branch_only = False
1347 elif self.manifest.default.sync_c:
1348 current_branch_only = True
1349
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001350 need_to_fetch = not (optimized_fetch and
1351 (ID_RE.match(self.revisionExpr) and
1352 self._CheckForSha1()))
1353 if (need_to_fetch and
1354 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1355 current_branch_only=current_branch_only,
1356 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001357 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001358
1359 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001360 self._InitMRef()
1361 else:
1362 self._InitMirrorHead()
1363 try:
1364 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1365 except OSError:
1366 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001368
1369 def PostRepoUpgrade(self):
1370 self._InitHooks()
1371
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001372 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001373 if self.manifest.isGitcClient:
1374 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001375 for copyfile in self.copyfiles:
1376 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001377 for linkfile in self.linkfiles:
1378 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379
Julien Camperguedd654222014-01-09 16:21:37 +01001380 def GetCommitRevisionId(self):
1381 """Get revisionId of a commit.
1382
1383 Use this method instead of GetRevisionId to get the id of the commit rather
1384 than the id of the current git object (for example, a tag)
1385
1386 """
1387 if not self.revisionExpr.startswith(R_TAGS):
1388 return self.GetRevisionId(self._allrefs)
1389
1390 try:
1391 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1392 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001393 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1394 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001395
David Pursehouse8a68ff92012-09-24 12:15:13 +09001396 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001397 if self.revisionId:
1398 return self.revisionId
1399
1400 rem = self.GetRemote(self.remote.name)
1401 rev = rem.ToLocal(self.revisionExpr)
1402
David Pursehouse8a68ff92012-09-24 12:15:13 +09001403 if all_refs is not None and rev in all_refs:
1404 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405
1406 try:
1407 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1408 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001409 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1410 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001411
Kevin Degiabaa7f32014-11-12 11:27:45 -07001412 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 """Perform only the local IO portion of the sync process.
1414 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001416 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001417 all_refs = self.bare_ref.all
1418 self.CleanPublishedCache(all_refs)
1419 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001420
David Pursehouse1d947b32012-10-25 12:23:11 +09001421 def _doff():
1422 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001423 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001424
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001425 head = self.work_git.GetHead()
1426 if head.startswith(R_HEADS):
1427 branch = head[len(R_HEADS):]
1428 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001429 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001430 except KeyError:
1431 head = None
1432 else:
1433 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001435 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001436 # Currently on a detached HEAD. The user is assumed to
1437 # not have any local modifications worth worrying about.
1438 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001439 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001440 syncbuf.fail(self, _PriorSyncFailedError())
1441 return
1442
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001443 if head == revid:
1444 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001445 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001446 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001447 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001448 # The copy/linkfile config may have changed.
1449 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001450 return
1451 else:
1452 lost = self._revlist(not_rev(revid), HEAD)
1453 if lost:
1454 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001455
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001457 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001458 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001459 syncbuf.fail(self, e)
1460 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001461 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001462 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001464 if head == revid:
1465 # No changes; don't do anything further.
1466 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001467 # The copy/linkfile config may have changed.
1468 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001469 return
1470
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001473 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001475 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001476 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001477 syncbuf.info(self,
1478 "leaving %s; does not track upstream",
1479 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001481 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001482 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001483 syncbuf.fail(self, e)
1484 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001485 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001486 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001488 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001489 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001491 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492 if not_merged:
1493 if upstream_gain:
1494 # The user has published this branch and some of those
1495 # commits are not yet merged upstream. We do not want
1496 # to rewrite the published commits so we punt.
1497 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001498 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001499 "branch %s is published (but not merged) and is now "
1500 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001501 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001502 elif pub == head:
1503 # All published commits are merged, and thus we are a
1504 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001505 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001506 syncbuf.later1(self, _doff)
1507 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001508
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001509 # Examine the local commits not in the remote. Find the
1510 # last one attributed to this user, if any.
1511 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001512 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001513 last_mine = None
1514 cnt_mine = 0
1515 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301516 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001517 if committer_email == self.UserEmail:
1518 last_mine = commit_id
1519 cnt_mine += 1
1520
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001521 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001522 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523
1524 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001525 syncbuf.fail(self, _DirtyError())
1526 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001528 # If the upstream switched on us, warn the user.
1529 #
1530 if branch.merge != self.revisionExpr:
1531 if branch.merge and self.revisionExpr:
1532 syncbuf.info(self,
1533 'manifest switched %s...%s',
1534 branch.merge,
1535 self.revisionExpr)
1536 elif branch.merge:
1537 syncbuf.info(self,
1538 'manifest no longer tracks %s',
1539 branch.merge)
1540
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001541 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001542 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001543 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001544 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001545 syncbuf.info(self,
1546 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001547 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001549 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001550 if not ID_RE.match(self.revisionExpr):
1551 # in case of manifest sync the revisionExpr might be a SHA1
1552 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001553 if not branch.merge.startswith('refs/'):
1554 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001555 branch.Save()
1556
Mike Pontillod3153822012-02-28 11:53:24 -08001557 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001558 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001559 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001560 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001561 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001562 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001564 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001565 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001566 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001567 syncbuf.fail(self, e)
1568 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001570 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001571
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001572 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573 # dest should already be an absolute path, but src is project relative
1574 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001575 abssrc = os.path.join(self.worktree, src)
1576 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001578 def AddLinkFile(self, src, dest, absdest):
1579 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001580 # make src relative path to dest
1581 absdestdir = os.path.dirname(absdest)
1582 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001583 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001584
James W. Mills24c13082012-04-12 15:04:13 -05001585 def AddAnnotation(self, name, value, keep):
1586 self.annotations.append(_Annotation(name, value, keep))
1587
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001588 def DownloadPatchSet(self, change_id, patch_id):
1589 """Download a single patch set of a single change to FETCH_HEAD.
1590 """
1591 remote = self.GetRemote(self.remote.name)
1592
1593 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001594 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001595 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001596 if GitCommand(self, cmd, bare=True).Wait() != 0:
1597 return None
1598 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001599 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001600 change_id,
1601 patch_id,
1602 self.bare_git.rev_parse('FETCH_HEAD'))
1603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001605# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606
Simran Basib9a1b732015-08-20 12:19:28 -07001607 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001608 """Create a new branch off the manifest's revision.
1609 """
Simran Basib9a1b732015-08-20 12:19:28 -07001610 if not branch_merge:
1611 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001612 head = self.work_git.GetHead()
1613 if head == (R_HEADS + name):
1614 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001615
David Pursehouse8a68ff92012-09-24 12:15:13 +09001616 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001617 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001618 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001619 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001620 capture_stdout=True,
1621 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001622
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001623 branch = self.GetBranch(name)
1624 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001625 branch.merge = branch_merge
1626 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1627 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001628 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001629
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001630 if head.startswith(R_HEADS):
1631 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001632 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001633 except KeyError:
1634 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001635 if revid and head and revid == head:
1636 ref = os.path.join(self.gitdir, R_HEADS + name)
1637 try:
1638 os.makedirs(os.path.dirname(ref))
1639 except OSError:
1640 pass
1641 _lwrite(ref, '%s\n' % revid)
1642 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1643 'ref: %s%s\n' % (R_HEADS, name))
1644 branch.Save()
1645 return True
1646
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001647 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001648 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001649 capture_stdout=True,
1650 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001651 branch.Save()
1652 return True
1653 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654
Wink Saville02d79452009-04-10 13:01:24 -07001655 def CheckoutBranch(self, name):
1656 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001657
1658 Args:
1659 name: The name of the branch to checkout.
1660
1661 Returns:
1662 True if the checkout succeeded; False if it didn't; None if the branch
1663 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001664 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001665 rev = R_HEADS + name
1666 head = self.work_git.GetHead()
1667 if head == rev:
1668 # Already on the branch
1669 #
1670 return True
Wink Saville02d79452009-04-10 13:01:24 -07001671
David Pursehouse8a68ff92012-09-24 12:15:13 +09001672 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001673 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001674 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001675 except KeyError:
1676 # Branch does not exist in this project
1677 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001678 return None
Wink Saville02d79452009-04-10 13:01:24 -07001679
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001680 if head.startswith(R_HEADS):
1681 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001682 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001683 except KeyError:
1684 head = None
1685
1686 if head == revid:
1687 # Same revision; just update HEAD to point to the new
1688 # target branch, but otherwise take no other action.
1689 #
1690 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1691 'ref: %s%s\n' % (R_HEADS, name))
1692 return True
1693
1694 return GitCommand(self,
1695 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001696 capture_stdout=True,
1697 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001698
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001699 def AbandonBranch(self, name):
1700 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001701
1702 Args:
1703 name: The name of the branch to abandon.
1704
1705 Returns:
1706 True if the abandon succeeded; False if it didn't; None if the branch
1707 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001708 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001709 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001710 all_refs = self.bare_ref.all
1711 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001712 # Doesn't exist
1713 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001714
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001715 head = self.work_git.GetHead()
1716 if head == rev:
1717 # We can't destroy the branch while we are sitting
1718 # on it. Switch to a detached HEAD.
1719 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001720 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001721
David Pursehouse8a68ff92012-09-24 12:15:13 +09001722 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001723 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001724 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1725 '%s\n' % revid)
1726 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001727 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001728
1729 return GitCommand(self,
1730 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001731 capture_stdout=True,
1732 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001733
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734 def PruneHeads(self):
1735 """Prune any topic branches already merged into upstream.
1736 """
1737 cb = self.CurrentBranch
1738 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001739 left = self._allrefs
1740 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 if name.startswith(R_HEADS):
1742 name = name[len(R_HEADS):]
1743 if cb is None or name != cb:
1744 kill.append(name)
1745
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001746 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 if cb is not None \
1748 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001749 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750 self.work_git.DetachHead(HEAD)
1751 kill.append(cb)
1752
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001754 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756 try:
1757 self.bare_git.DetachHead(rev)
1758
1759 b = ['branch', '-d']
1760 b.extend(kill)
1761 b = GitCommand(self, b, bare=True,
1762 capture_stdout=True,
1763 capture_stderr=True)
1764 b.Wait()
1765 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001766 if ID_RE.match(old):
1767 self.bare_git.DetachHead(old)
1768 else:
1769 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001770 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001771
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001772 for branch in kill:
1773 if (R_HEADS + branch) not in left:
1774 self.CleanPublishedCache()
1775 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001776
1777 if cb and cb not in kill:
1778 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001779 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780
1781 kept = []
1782 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001783 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 branch = self.GetBranch(branch)
1785 base = branch.LocalMerge
1786 if not base:
1787 base = rev
1788 kept.append(ReviewableBranch(self, branch, base))
1789 return kept
1790
1791
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001792# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001793
1794 def GetRegisteredSubprojects(self):
1795 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001796
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001797 def rec(subprojects):
1798 if not subprojects:
1799 return
1800 result.extend(subprojects)
1801 for p in subprojects:
1802 rec(p.subprojects)
1803 rec(self.subprojects)
1804 return result
1805
1806 def _GetSubmodules(self):
1807 # Unfortunately we cannot call `git submodule status --recursive` here
1808 # because the working tree might not exist yet, and it cannot be used
1809 # without a working tree in its current implementation.
1810
1811 def get_submodules(gitdir, rev):
1812 # Parse .gitmodules for submodule sub_paths and sub_urls
1813 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1814 if not sub_paths:
1815 return []
1816 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1817 # revision of submodule repository
1818 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1819 submodules = []
1820 for sub_path, sub_url in zip(sub_paths, sub_urls):
1821 try:
1822 sub_rev = sub_revs[sub_path]
1823 except KeyError:
1824 # Ignore non-exist submodules
1825 continue
1826 submodules.append((sub_rev, sub_path, sub_url))
1827 return submodules
1828
1829 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1830 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001831
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001832 def parse_gitmodules(gitdir, rev):
1833 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1834 try:
Anthony King7bdac712014-07-16 12:56:40 +01001835 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1836 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001837 except GitError:
1838 return [], []
1839 if p.Wait() != 0:
1840 return [], []
1841
1842 gitmodules_lines = []
1843 fd, temp_gitmodules_path = tempfile.mkstemp()
1844 try:
1845 os.write(fd, p.stdout)
1846 os.close(fd)
1847 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001848 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1849 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001850 if p.Wait() != 0:
1851 return [], []
1852 gitmodules_lines = p.stdout.split('\n')
1853 except GitError:
1854 return [], []
1855 finally:
1856 os.remove(temp_gitmodules_path)
1857
1858 names = set()
1859 paths = {}
1860 urls = {}
1861 for line in gitmodules_lines:
1862 if not line:
1863 continue
1864 m = re_path.match(line)
1865 if m:
1866 names.add(m.group(1))
1867 paths[m.group(1)] = m.group(2)
1868 continue
1869 m = re_url.match(line)
1870 if m:
1871 names.add(m.group(1))
1872 urls[m.group(1)] = m.group(2)
1873 continue
1874 names = sorted(names)
1875 return ([paths.get(name, '') for name in names],
1876 [urls.get(name, '') for name in names])
1877
1878 def git_ls_tree(gitdir, rev, paths):
1879 cmd = ['ls-tree', rev, '--']
1880 cmd.extend(paths)
1881 try:
Anthony King7bdac712014-07-16 12:56:40 +01001882 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1883 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001884 except GitError:
1885 return []
1886 if p.Wait() != 0:
1887 return []
1888 objects = {}
1889 for line in p.stdout.split('\n'):
1890 if not line.strip():
1891 continue
1892 object_rev, object_path = line.split()[2:4]
1893 objects[object_path] = object_rev
1894 return objects
1895
1896 try:
1897 rev = self.GetRevisionId()
1898 except GitError:
1899 return []
1900 return get_submodules(self.gitdir, rev)
1901
1902 def GetDerivedSubprojects(self):
1903 result = []
1904 if not self.Exists:
1905 # If git repo does not exist yet, querying its submodules will
1906 # mess up its states; so return here.
1907 return result
1908 for rev, path, url in self._GetSubmodules():
1909 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001910 relpath, worktree, gitdir, objdir = \
1911 self.manifest.GetSubprojectPaths(self, name, path)
1912 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001913 if project:
1914 result.extend(project.GetDerivedSubprojects())
1915 continue
David James8d201162013-10-11 17:03:19 -07001916
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001917 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001918 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001919 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001920 review=self.remote.review,
1921 revision=self.remote.revision)
1922 subproject = Project(manifest=self.manifest,
1923 name=name,
1924 remote=remote,
1925 gitdir=gitdir,
1926 objdir=objdir,
1927 worktree=worktree,
1928 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001929 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001930 revisionId=rev,
1931 rebase=self.rebase,
1932 groups=self.groups,
1933 sync_c=self.sync_c,
1934 sync_s=self.sync_s,
1935 parent=self,
1936 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001937 result.append(subproject)
1938 result.extend(subproject.GetDerivedSubprojects())
1939 return result
1940
1941
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001942# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001943 def _CheckForSha1(self):
1944 try:
1945 # if revision (sha or tag) is not present then following function
1946 # throws an error.
1947 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1948 return True
1949 except GitError:
1950 # There is no such persistent revision. We have to fetch it.
1951 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001952
Julien Campergue335f5ef2013-10-16 11:02:35 +02001953 def _FetchArchive(self, tarpath, cwd=None):
1954 cmd = ['archive', '-v', '-o', tarpath]
1955 cmd.append('--remote=%s' % self.remote.url)
1956 cmd.append('--prefix=%s/' % self.relpath)
1957 cmd.append(self.revisionExpr)
1958
1959 command = GitCommand(self, cmd, cwd=cwd,
1960 capture_stdout=True,
1961 capture_stderr=True)
1962
1963 if command.Wait() != 0:
1964 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1965
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001966 def _RemoteFetch(self, name=None,
1967 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001968 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001969 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001970 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001971 no_tags=False,
1972 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001973
1974 is_sha1 = False
1975 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001976 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001977
David Pursehouse9bc422f2014-04-15 10:28:56 +09001978 # The depth should not be used when fetching to a mirror because
1979 # it will result in a shallow repository that cannot be cloned or
1980 # fetched from.
1981 if not self.manifest.IsMirror:
1982 if self.clone_depth:
1983 depth = self.clone_depth
1984 else:
1985 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001986 # The repo project should never be synced with partial depth
1987 if self.relpath == '.repo/repo':
1988 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001989
Shawn Pearce69e04d82014-01-29 12:48:54 -08001990 if depth:
1991 current_branch_only = True
1992
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001993 if ID_RE.match(self.revisionExpr) is not None:
1994 is_sha1 = True
1995
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001996 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001997 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001998 # this is a tag and its sha1 value should never change
1999 tag_name = self.revisionExpr[len(R_TAGS):]
2000
2001 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05002002 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002003 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002004 if is_sha1 and not depth:
2005 # When syncing a specific commit and --depth is not set:
2006 # * if upstream is explicitly specified and is not a sha1, fetch only
2007 # upstream as users expect only upstream to be fetch.
2008 # Note: The commit might not be in upstream in which case the sync
2009 # will fail.
2010 # * otherwise, fetch all branches to make sure we end up with the
2011 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002012 if self.upstream:
2013 current_branch_only = not ID_RE.match(self.upstream)
2014 else:
2015 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002016
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017 if not name:
2018 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002019
2020 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002021 remote = self.GetRemote(name)
2022 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002023 ssh_proxy = True
2024
Shawn O. Pearce88443382010-10-08 10:02:09 +02002025 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002026 if alt_dir and 'objects' == os.path.basename(alt_dir):
2027 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002028 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2029 remote = self.GetRemote(name)
2030
David Pursehouse8a68ff92012-09-24 12:15:13 +09002031 all_refs = self.bare_ref.all
2032 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002033 tmp = set()
2034
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302035 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002036 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002037 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002038 all_refs[r] = ref_id
2039 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002040 continue
2041
David Pursehouse8a68ff92012-09-24 12:15:13 +09002042 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002043 continue
2044
David Pursehouse8a68ff92012-09-24 12:15:13 +09002045 r = 'refs/_alt/%s' % ref_id
2046 all_refs[r] = ref_id
2047 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002048 tmp.add(r)
2049
Shawn O. Pearce88443382010-10-08 10:02:09 +02002050 tmp_packed = ''
2051 old_packed = ''
2052
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302053 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002054 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002055 tmp_packed += line
2056 if r not in tmp:
2057 old_packed += line
2058
2059 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002060 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002061 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002062
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002063 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002064
Conley Owensf97e8382015-01-21 11:12:46 -08002065 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002066 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002067 else:
2068 # If this repo has shallow objects, then we don't know which refs have
2069 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2070 # do this with projects that don't have shallow objects, since it is less
2071 # efficient.
2072 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2073 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002074
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002075 if quiet:
2076 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002077 if not self.worktree:
2078 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002079 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002080
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002081 # If using depth then we should not get all the tags since they may
2082 # be outside of the depth.
2083 if no_tags or depth:
2084 cmd.append('--no-tags')
2085 else:
2086 cmd.append('--tags')
2087
David Pursehouse74cfd272015-10-14 10:50:15 +09002088 if prune:
2089 cmd.append('--prune')
2090
Conley Owens80b87fe2014-05-09 17:13:44 -07002091 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002092 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002093 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002094 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002095 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002096 spec.append('tag')
2097 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002098
David Pursehouse403b64e2015-04-27 10:41:33 +09002099 if not self.manifest.IsMirror:
2100 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002101 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002102 # Shallow checkout of a specific commit, fetch from that commit and not
2103 # the heads only as the commit might be deeper in the history.
2104 spec.append(branch)
2105 else:
2106 if is_sha1:
2107 branch = self.upstream
2108 if branch is not None and branch.strip():
2109 if not branch.startswith('refs/'):
2110 branch = R_HEADS + branch
2111 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002112 cmd.extend(spec)
2113
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002114 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002115 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002116 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002117 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002118 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002119 ok = True
2120 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002121 # If needed, run the 'git remote prune' the first time through the loop
2122 elif (not _i and
2123 "error:" in gitcmd.stderr and
2124 "git remote prune" in gitcmd.stderr):
2125 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002126 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002127 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002128 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002129 break
2130 continue
Brian Harring14a66742012-09-28 20:21:57 -07002131 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002132 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2133 # in sha1 mode, we just tried sync'ing from the upstream field; it
2134 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002135 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002136 elif ret < 0:
2137 # Git died with a signal, exit immediately
2138 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002139 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140
2141 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002142 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 if old_packed != '':
2144 _lwrite(packed_refs, old_packed)
2145 else:
2146 os.remove(packed_refs)
2147 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002148
2149 if is_sha1 and current_branch_only and self.upstream:
2150 # We just synced the upstream given branch; verify we
2151 # got what we wanted, else trigger a second run of all
2152 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002153 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002154 if not depth:
2155 # Avoid infinite recursion when depth is True (since depth implies
2156 # current_branch_only)
2157 return self._RemoteFetch(name=name, current_branch_only=False,
2158 initial=False, quiet=quiet, alt_dir=alt_dir)
2159 if self.clone_depth:
2160 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002161 return self._RemoteFetch(name=name,
2162 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002163 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002164
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002165 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002166
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002167 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002168 if initial and \
2169 (self.manifest.manifestProject.config.GetString('repo.depth') or
2170 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002171 return False
2172
2173 remote = self.GetRemote(self.remote.name)
2174 bundle_url = remote.url + '/clone.bundle'
2175 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002176 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2177 'persistent-http',
2178 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002179 return False
2180
2181 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2182 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2183
2184 exist_dst = os.path.exists(bundle_dst)
2185 exist_tmp = os.path.exists(bundle_tmp)
2186
2187 if not initial and not exist_dst and not exist_tmp:
2188 return False
2189
2190 if not exist_dst:
2191 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2192 if not exist_dst:
2193 return False
2194
2195 cmd = ['fetch']
2196 if quiet:
2197 cmd.append('--quiet')
2198 if not self.worktree:
2199 cmd.append('--update-head-ok')
2200 cmd.append(bundle_dst)
2201 for f in remote.fetch:
2202 cmd.append(str(f))
2203 cmd.append('refs/tags/*:refs/tags/*')
2204
2205 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002206 if os.path.exists(bundle_dst):
2207 os.remove(bundle_dst)
2208 if os.path.exists(bundle_tmp):
2209 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002210 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002211
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002212 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002213 if os.path.exists(dstPath):
2214 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002215
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002216 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002217 if quiet:
2218 cmd += ['--silent']
2219 if os.path.exists(tmpPath):
2220 size = os.stat(tmpPath).st_size
2221 if size >= 1024:
2222 cmd += ['--continue-at', '%d' % (size,)]
2223 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002224 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002225 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2226 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002227 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002228 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002229 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002230 if srcUrl.startswith('persistent-'):
2231 srcUrl = srcUrl[len('persistent-'):]
2232 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002233
Dave Borowitz137d0132015-01-02 11:12:54 -08002234 if IsTrace():
2235 Trace('%s', ' '.join(cmd))
2236 try:
2237 proc = subprocess.Popen(cmd)
2238 except OSError:
2239 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002240
Dave Borowitz137d0132015-01-02 11:12:54 -08002241 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002242
Dave Borowitz137d0132015-01-02 11:12:54 -08002243 if curlret == 22:
2244 # From curl man page:
2245 # 22: HTTP page not retrieved. The requested url was not found or
2246 # returned another error with the HTTP error code being 400 or above.
2247 # This return code only appears if -f, --fail is used.
2248 if not quiet:
2249 print("Server does not provide clone.bundle; ignoring.",
2250 file=sys.stderr)
2251 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002252
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002253 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002254 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002255 os.rename(tmpPath, dstPath)
2256 return True
2257 else:
2258 os.remove(tmpPath)
2259 return False
2260 else:
2261 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002262
Kris Giesingc8d882a2014-12-23 13:02:32 -08002263 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002264 try:
2265 with open(path) as f:
2266 if f.read(16) == '# v2 git bundle\n':
2267 return True
2268 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002269 if not quiet:
2270 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002271 return False
2272 except OSError:
2273 return False
2274
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002275 def _Checkout(self, rev, quiet=False):
2276 cmd = ['checkout']
2277 if quiet:
2278 cmd.append('-q')
2279 cmd.append(rev)
2280 cmd.append('--')
2281 if GitCommand(self, cmd).Wait() != 0:
2282 if self._allrefs:
2283 raise GitError('%s checkout %s ' % (self.name, rev))
2284
Anthony King7bdac712014-07-16 12:56:40 +01002285 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002286 cmd = ['cherry-pick']
2287 cmd.append(rev)
2288 cmd.append('--')
2289 if GitCommand(self, cmd).Wait() != 0:
2290 if self._allrefs:
2291 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2292
Anthony King7bdac712014-07-16 12:56:40 +01002293 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002294 cmd = ['revert']
2295 cmd.append('--no-edit')
2296 cmd.append(rev)
2297 cmd.append('--')
2298 if GitCommand(self, cmd).Wait() != 0:
2299 if self._allrefs:
2300 raise GitError('%s revert %s ' % (self.name, rev))
2301
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002302 def _ResetHard(self, rev, quiet=True):
2303 cmd = ['reset', '--hard']
2304 if quiet:
2305 cmd.append('-q')
2306 cmd.append(rev)
2307 if GitCommand(self, cmd).Wait() != 0:
2308 raise GitError('%s reset --hard %s ' % (self.name, rev))
2309
Anthony King7bdac712014-07-16 12:56:40 +01002310 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002311 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002312 if onto is not None:
2313 cmd.extend(['--onto', onto])
2314 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002315 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002316 raise GitError('%s rebase %s ' % (self.name, upstream))
2317
Pierre Tardy3d125942012-05-04 12:18:12 +02002318 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002319 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002320 if ffonly:
2321 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002322 if GitCommand(self, cmd).Wait() != 0:
2323 raise GitError('%s merge %s ' % (self.name, head))
2324
Kevin Degiabaa7f32014-11-12 11:27:45 -07002325 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002326 init_git_dir = not os.path.exists(self.gitdir)
2327 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002328 try:
2329 # Initialize the bare repository, which contains all of the objects.
2330 if init_obj_dir:
2331 os.makedirs(self.objdir)
2332 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002333
Kevin Degib1a07b82015-07-27 13:33:43 -06002334 # If we have a separate directory to hold refs, initialize it as well.
2335 if self.objdir != self.gitdir:
2336 if init_git_dir:
2337 os.makedirs(self.gitdir)
2338
2339 if init_obj_dir or init_git_dir:
2340 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2341 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002342 try:
2343 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2344 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002345 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002346 print("Retrying clone after deleting %s" %
2347 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002348 try:
2349 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002350 if self.worktree and os.path.exists(os.path.realpath
2351 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002352 shutil.rmtree(os.path.realpath(self.worktree))
2353 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2354 except:
2355 raise e
2356 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002357
Kevin Degi384b3c52014-10-16 16:02:58 -06002358 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002359 mp = self.manifest.manifestProject
2360 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002361
Kevin Degib1a07b82015-07-27 13:33:43 -06002362 if ref_dir or mirror_git:
2363 if not mirror_git:
2364 mirror_git = os.path.join(ref_dir, self.name + '.git')
2365 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2366 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002367
Kevin Degib1a07b82015-07-27 13:33:43 -06002368 if os.path.exists(mirror_git):
2369 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002370
Kevin Degib1a07b82015-07-27 13:33:43 -06002371 elif os.path.exists(repo_git):
2372 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002373
Kevin Degib1a07b82015-07-27 13:33:43 -06002374 else:
2375 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002376
Kevin Degib1a07b82015-07-27 13:33:43 -06002377 if ref_dir:
2378 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2379 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002380
Kevin Degib1a07b82015-07-27 13:33:43 -06002381 self._UpdateHooks()
2382
2383 m = self.manifest.manifestProject.config
2384 for key in ['user.name', 'user.email']:
2385 if m.Has(key, include_defaults=False):
2386 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002387 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002388 if self.manifest.IsMirror:
2389 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002390 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002391 self.config.SetString('core.bare', None)
2392 except Exception:
2393 if init_obj_dir and os.path.exists(self.objdir):
2394 shutil.rmtree(self.objdir)
2395 if init_git_dir and os.path.exists(self.gitdir):
2396 shutil.rmtree(self.gitdir)
2397 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002398
Jimmie Westera0444582012-10-24 13:44:42 +02002399 def _UpdateHooks(self):
2400 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002401 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002402
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002403 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002404 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002405 if not os.path.exists(hooks):
2406 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002407 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002408 name = os.path.basename(stock_hook)
2409
Victor Boivie65e0f352011-04-18 11:23:29 +02002410 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002411 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002412 # Don't install a Gerrit Code Review hook if this
2413 # project does not appear to use it for reviews.
2414 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002415 # Since the manifest project is one of those, but also
2416 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002417 continue
2418
2419 dst = os.path.join(hooks, name)
2420 if os.path.islink(dst):
2421 continue
2422 if os.path.exists(dst):
2423 if filecmp.cmp(stock_hook, dst, shallow=False):
2424 os.remove(dst)
2425 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002426 _warn("%s: Not replacing locally modified %s hook",
2427 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002428 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002429 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002430 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002431 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002432 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002433 raise GitError('filesystem must support symlinks')
2434 else:
2435 raise
2436
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002437 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002438 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002440 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002441 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002442 remote.review = self.remote.review
2443 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002444
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002445 if self.worktree:
2446 remote.ResetFetch(mirror=False)
2447 else:
2448 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002449 remote.Save()
2450
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451 def _InitMRef(self):
2452 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002453 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002454
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002455 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002456 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002457
2458 def _InitAnyMRef(self, ref):
2459 cur = self.bare_ref.symref(ref)
2460
2461 if self.revisionId:
2462 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2463 msg = 'manifest set to %s' % self.revisionId
2464 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002465 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002466 else:
2467 remote = self.GetRemote(self.remote.name)
2468 dst = remote.ToLocal(self.revisionExpr)
2469 if cur != dst:
2470 msg = 'manifest set to %s' % self.revisionExpr
2471 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002472
Kevin Degi384b3c52014-10-16 16:02:58 -06002473 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002474 symlink_files = self.shareable_files[:]
2475 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002476 if share_refs:
2477 symlink_files += self.working_tree_files
2478 symlink_dirs += self.working_tree_dirs
2479 to_symlink = symlink_files + symlink_dirs
2480 for name in set(to_symlink):
2481 dst = os.path.realpath(os.path.join(destdir, name))
2482 if os.path.lexists(dst):
2483 src = os.path.realpath(os.path.join(srcdir, name))
2484 # Fail if the links are pointing to the wrong place
2485 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002486 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002487 'work tree. If you\'re comfortable with the '
2488 'possibility of losing the work tree\'s git metadata,'
2489 ' use `repo sync --force-sync {0}` to '
2490 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002491
David James8d201162013-10-11 17:03:19 -07002492 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2493 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2494
2495 Args:
2496 gitdir: The bare git repository. Must already be initialized.
2497 dotgit: The repository you would like to initialize.
2498 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2499 Only one work tree can store refs under a given |gitdir|.
2500 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2501 This saves you the effort of initializing |dotgit| yourself.
2502 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002503 symlink_files = self.shareable_files[:]
2504 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002505 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002506 symlink_files += self.working_tree_files
2507 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002508 to_symlink = symlink_files + symlink_dirs
2509
2510 to_copy = []
2511 if copy_all:
2512 to_copy = os.listdir(gitdir)
2513
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002514 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002515 for name in set(to_copy).union(to_symlink):
2516 try:
2517 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002518 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002519
Kevin Degi384b3c52014-10-16 16:02:58 -06002520 if os.path.lexists(dst):
2521 continue
David James8d201162013-10-11 17:03:19 -07002522
2523 # If the source dir doesn't exist, create an empty dir.
2524 if name in symlink_dirs and not os.path.lexists(src):
2525 os.makedirs(src)
2526
Conley Owens80b87fe2014-05-09 17:13:44 -07002527 # If the source file doesn't exist, ensure the destination
2528 # file doesn't either.
2529 if name in symlink_files and not os.path.lexists(src):
2530 try:
2531 os.remove(dst)
2532 except OSError:
2533 pass
2534
David James8d201162013-10-11 17:03:19 -07002535 if name in to_symlink:
2536 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2537 elif copy_all and not os.path.islink(dst):
2538 if os.path.isdir(src):
2539 shutil.copytree(src, dst)
2540 elif os.path.isfile(src):
2541 shutil.copy(src, dst)
2542 except OSError as e:
2543 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002544 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002545 else:
2546 raise
2547
Kevin Degiabaa7f32014-11-12 11:27:45 -07002548 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002550 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002551 try:
2552 if init_dotgit:
2553 os.makedirs(dotgit)
2554 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2555 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002556
Kevin Degiabaa7f32014-11-12 11:27:45 -07002557 try:
2558 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2559 except GitError as e:
2560 if force_sync:
2561 try:
2562 shutil.rmtree(dotgit)
2563 return self._InitWorkTree(force_sync=False)
2564 except:
2565 raise e
2566 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002567
Kevin Degib1a07b82015-07-27 13:33:43 -06002568 if init_dotgit:
2569 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002570
Kevin Degib1a07b82015-07-27 13:33:43 -06002571 cmd = ['read-tree', '--reset', '-u']
2572 cmd.append('-v')
2573 cmd.append(HEAD)
2574 if GitCommand(self, cmd).Wait() != 0:
2575 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002576
Kevin Degib1a07b82015-07-27 13:33:43 -06002577 self._CopyAndLinkFiles()
2578 except Exception:
2579 if init_dotgit:
2580 shutil.rmtree(dotgit)
2581 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002582
2583 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002584 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002585
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002586 def _revlist(self, *args, **kw):
2587 a = []
2588 a.extend(args)
2589 a.append('--')
2590 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002591
2592 @property
2593 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002594 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002596 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002597 """Get logs between two revisions of this project."""
2598 comp = '..'
2599 if rev1:
2600 revs = [rev1]
2601 if rev2:
2602 revs.extend([comp, rev2])
2603 cmd = ['log', ''.join(revs)]
2604 out = DiffColoring(self.config)
2605 if out.is_on and color:
2606 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002607 if pretty_format is not None:
2608 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002609 if oneline:
2610 cmd.append('--oneline')
2611
2612 try:
2613 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2614 if log.Wait() == 0:
2615 return log.stdout
2616 except GitError:
2617 # worktree may not exist if groups changed for example. In that case,
2618 # try in gitdir instead.
2619 if not os.path.exists(self.worktree):
2620 return self.bare_git.log(*cmd[1:])
2621 else:
2622 raise
2623 return None
2624
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002625 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2626 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002627 """Get the list of logs from this revision to given revisionId"""
2628 logs = {}
2629 selfId = self.GetRevisionId(self._allrefs)
2630 toId = toProject.GetRevisionId(toProject._allrefs)
2631
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002632 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2633 pretty_format=pretty_format)
2634 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2635 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002636 return logs
2637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002638 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002639
David James8d201162013-10-11 17:03:19 -07002640 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002641 self._project = project
2642 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002643 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645 def LsOthers(self):
2646 p = GitCommand(self._project,
2647 ['ls-files',
2648 '-z',
2649 '--others',
2650 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002651 bare=False,
David James8d201162013-10-11 17:03:19 -07002652 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002653 capture_stdout=True,
2654 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002655 if p.Wait() == 0:
2656 out = p.stdout
2657 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002658 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002659 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002660 return []
2661
2662 def DiffZ(self, name, *args):
2663 cmd = [name]
2664 cmd.append('-z')
2665 cmd.extend(args)
2666 p = GitCommand(self._project,
2667 cmd,
David James8d201162013-10-11 17:03:19 -07002668 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002669 bare=False,
2670 capture_stdout=True,
2671 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002672 try:
2673 out = p.process.stdout.read()
2674 r = {}
2675 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002676 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002677 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002678 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002679 info = next(out)
2680 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002681 except StopIteration:
2682 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002683
2684 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002685
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 def __init__(self, path, omode, nmode, oid, nid, state):
2687 self.path = path
2688 self.src_path = None
2689 self.old_mode = omode
2690 self.new_mode = nmode
2691 self.old_id = oid
2692 self.new_id = nid
2693
2694 if len(state) == 1:
2695 self.status = state
2696 self.level = None
2697 else:
2698 self.status = state[:1]
2699 self.level = state[1:]
2700 while self.level.startswith('0'):
2701 self.level = self.level[1:]
2702
2703 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002704 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002705 if info.status in ('R', 'C'):
2706 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002707 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002708 r[info.path] = info
2709 return r
2710 finally:
2711 p.Wait()
2712
2713 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002714 if self._bare:
2715 path = os.path.join(self._project.gitdir, HEAD)
2716 else:
2717 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002718 try:
2719 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002720 except IOError as e:
2721 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002722 try:
2723 line = fd.read()
2724 finally:
2725 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302726 try:
2727 line = line.decode()
2728 except AttributeError:
2729 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002730 if line.startswith('ref: '):
2731 return line[5:-1]
2732 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002733
2734 def SetHead(self, ref, message=None):
2735 cmdv = []
2736 if message is not None:
2737 cmdv.extend(['-m', message])
2738 cmdv.append(HEAD)
2739 cmdv.append(ref)
2740 self.symbolic_ref(*cmdv)
2741
2742 def DetachHead(self, new, message=None):
2743 cmdv = ['--no-deref']
2744 if message is not None:
2745 cmdv.extend(['-m', message])
2746 cmdv.append(HEAD)
2747 cmdv.append(new)
2748 self.update_ref(*cmdv)
2749
2750 def UpdateRef(self, name, new, old=None,
2751 message=None,
2752 detach=False):
2753 cmdv = []
2754 if message is not None:
2755 cmdv.extend(['-m', message])
2756 if detach:
2757 cmdv.append('--no-deref')
2758 cmdv.append(name)
2759 cmdv.append(new)
2760 if old is not None:
2761 cmdv.append(old)
2762 self.update_ref(*cmdv)
2763
2764 def DeleteRef(self, name, old=None):
2765 if not old:
2766 old = self.rev_parse(name)
2767 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002768 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002769
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002770 def rev_list(self, *args, **kw):
2771 if 'format' in kw:
2772 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2773 else:
2774 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002775 cmdv.extend(args)
2776 p = GitCommand(self._project,
2777 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002778 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002779 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002780 capture_stdout=True,
2781 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002782 r = []
2783 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002784 if line[-1] == '\n':
2785 line = line[:-1]
2786 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002787 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002788 raise GitError('%s rev-list %s: %s' %
2789 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002790 return r
2791
2792 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002793 """Allow arbitrary git commands using pythonic syntax.
2794
2795 This allows you to do things like:
2796 git_obj.rev_parse('HEAD')
2797
2798 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2799 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002800 Any other positional arguments will be passed to the git command, and the
2801 following keyword arguments are supported:
2802 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002803
2804 Args:
2805 name: The name of the git command to call. Any '_' characters will
2806 be replaced with '-'.
2807
2808 Returns:
2809 A callable object that will try to call git with the named command.
2810 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002811 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002812
Dave Borowitz091f8932012-10-23 17:01:04 -07002813 def runner(*args, **kwargs):
2814 cmdv = []
2815 config = kwargs.pop('config', None)
2816 for k in kwargs:
2817 raise TypeError('%s() got an unexpected keyword argument %r'
2818 % (name, k))
2819 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002820 if not git_require((1, 7, 2)):
2821 raise ValueError('cannot set config on command line for %s()'
2822 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302823 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002824 cmdv.append('-c')
2825 cmdv.append('%s=%s' % (k, v))
2826 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002827 cmdv.extend(args)
2828 p = GitCommand(self._project,
2829 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002830 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002831 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002832 capture_stdout=True,
2833 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002834 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002835 raise GitError('%s %s: %s' %
2836 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002837 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302838 try:
Conley Owensedd01512013-09-26 12:59:58 -07002839 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302840 except AttributeError:
2841 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002842 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2843 return r[:-1]
2844 return r
2845 return runner
2846
2847
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002848class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002849
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002850 def __str__(self):
2851 return 'prior sync failed; rebase still in progress'
2852
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002853
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002854class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002855
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002856 def __str__(self):
2857 return 'contains uncommitted changes'
2858
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002859
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002860class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002861
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002862 def __init__(self, project, text):
2863 self.project = project
2864 self.text = text
2865
2866 def Print(self, syncbuf):
2867 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2868 syncbuf.out.nl()
2869
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002870
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002871class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002872
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002873 def __init__(self, project, why):
2874 self.project = project
2875 self.why = why
2876
2877 def Print(self, syncbuf):
2878 syncbuf.out.fail('error: %s/: %s',
2879 self.project.relpath,
2880 str(self.why))
2881 syncbuf.out.nl()
2882
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002883
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002884class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002885
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002886 def __init__(self, project, action):
2887 self.project = project
2888 self.action = action
2889
2890 def Run(self, syncbuf):
2891 out = syncbuf.out
2892 out.project('project %s/', self.project.relpath)
2893 out.nl()
2894 try:
2895 self.action()
2896 out.nl()
2897 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002898 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002899 out.nl()
2900 return False
2901
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002902
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002903class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002904
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002905 def __init__(self, config):
2906 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002907 self.project = self.printer('header', attr='bold')
2908 self.info = self.printer('info')
2909 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002910
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002911
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002912class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002913
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002914 def __init__(self, config, detach_head=False):
2915 self._messages = []
2916 self._failures = []
2917 self._later_queue1 = []
2918 self._later_queue2 = []
2919
2920 self.out = _SyncColoring(config)
2921 self.out.redirect(sys.stderr)
2922
2923 self.detach_head = detach_head
2924 self.clean = True
2925
2926 def info(self, project, fmt, *args):
2927 self._messages.append(_InfoMessage(project, fmt % args))
2928
2929 def fail(self, project, err=None):
2930 self._failures.append(_Failure(project, err))
2931 self.clean = False
2932
2933 def later1(self, project, what):
2934 self._later_queue1.append(_Later(project, what))
2935
2936 def later2(self, project, what):
2937 self._later_queue2.append(_Later(project, what))
2938
2939 def Finish(self):
2940 self._PrintMessages()
2941 self._RunLater()
2942 self._PrintMessages()
2943 return self.clean
2944
2945 def _RunLater(self):
2946 for q in ['_later_queue1', '_later_queue2']:
2947 if not self._RunQueue(q):
2948 return
2949
2950 def _RunQueue(self, queue):
2951 for m in getattr(self, queue):
2952 if not m.Run(self):
2953 self.clean = False
2954 return False
2955 setattr(self, queue, [])
2956 return True
2957
2958 def _PrintMessages(self):
2959 for m in self._messages:
2960 m.Print(self)
2961 for m in self._failures:
2962 m.Print(self)
2963
2964 self._messages = []
2965 self._failures = []
2966
2967
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002968class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002969
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002970 """A special project housed under .repo.
2971 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002972
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002973 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002975 manifest=manifest,
2976 name=name,
2977 gitdir=gitdir,
2978 objdir=gitdir,
2979 worktree=worktree,
2980 remote=RemoteSpec('origin'),
2981 relpath='.repo/%s' % name,
2982 revisionExpr='refs/heads/master',
2983 revisionId=None,
2984 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002985
2986 def PreSync(self):
2987 if self.Exists:
2988 cb = self.CurrentBranch
2989 if cb:
2990 base = self.GetBranch(cb).merge
2991 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002992 self.revisionExpr = base
2993 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002994
Anthony King7bdac712014-07-16 12:56:40 +01002995 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002996 """ Prepare MetaProject for manifest branch switch
2997 """
2998
2999 # detach and delete manifest branch, allowing a new
3000 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003001 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02003002 self.Sync_LocalHalf(syncbuf)
3003 syncbuf.Finish()
3004
3005 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003006 ['update-ref', '-d', 'refs/heads/default'],
3007 capture_stdout=True,
3008 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003009
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003010 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003011 def LastFetch(self):
3012 try:
3013 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3014 return os.path.getmtime(fh)
3015 except OSError:
3016 return 0
3017
3018 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003019 def HasChanges(self):
3020 """Has the remote received new commits not yet checked out?
3021 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003022 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003023 return False
3024
David Pursehouse8a68ff92012-09-24 12:15:13 +09003025 all_refs = self.bare_ref.all
3026 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003027 head = self.work_git.GetHead()
3028 if head.startswith(R_HEADS):
3029 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003030 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003031 except KeyError:
3032 head = None
3033
3034 if revid == head:
3035 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003036 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003037 return True
3038 return False