blob: de5c79158dcbd5f7f606999fdb9242c08171ba7f [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, \
34 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070038import platform_utils
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,
180 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800181 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700182 people,
Brian Harring435370c2012-07-28 15:37:04 -0700183 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400184 draft=draft,
185 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700187 def GetPublishedRefs(self):
188 refs = {}
189 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700190 self.branch.remote.SshReviewUrl(self.project.UserEmail),
191 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700192 for line in output.split('\n'):
193 try:
194 (sha, ref) = line.split()
195 refs[sha] = ref
196 except ValueError:
197 pass
198
199 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700201
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700203
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204 def __init__(self, config):
205 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100206 self.project = self.printer('header', attr='bold')
207 self.branch = self.printer('header', attr='bold')
208 self.nobranch = self.printer('nobranch', fg='red')
209 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
Anthony King7bdac712014-07-16 12:56:40 +0100211 self.added = self.printer('added', fg='green')
212 self.changed = self.printer('changed', fg='red')
213 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
215
216class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700217
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 def __init__(self, config):
219 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100220 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700222
Anthony King7bdac712014-07-16 12:56:40 +0100223class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700224
James W. Mills24c13082012-04-12 15:04:13 -0500225 def __init__(self, name, value, keep):
226 self.name = name
227 self.value = value
228 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700230
Anthony King7bdac712014-07-16 12:56:40 +0100231class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700232
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800233 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 self.src = src
235 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800236 self.abs_src = abssrc
237 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
239 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800240 src = self.abs_src
241 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 # copy file if it does not exist or is out of date
243 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
244 try:
245 # remove existing file first, since it might be read-only
246 if os.path.exists(dest):
247 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400248 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200249 dest_dir = os.path.dirname(dest)
250 if not os.path.isdir(dest_dir):
251 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 shutil.copy(src, dest)
253 # make the file read-only
254 mode = os.stat(dest)[stat.ST_MODE]
255 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
256 os.chmod(dest, mode)
257 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700258 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
Wink Saville4c426ef2015-06-03 08:05:17 -0700263 def __init__(self, git_worktree, src, dest, relsrc, absdest):
264 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500265 self.src = src
266 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700267 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500268 self.abs_dest = absdest
269
Wink Saville4c426ef2015-06-03 08:05:17 -0700270 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700272 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500273 try:
274 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800275 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700278 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500279 if not os.path.isdir(dest_dir):
280 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700281 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500282 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700283 _error('Cannot link file %s to %s', relSrc, absDest)
284
285 def _Link(self):
286 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
287 on the src linking all of the files in the source in to the destination
288 directory.
289 """
290 # We use the absSrc to handle the situation where the current directory
291 # is not the root of the repo
292 absSrc = os.path.join(self.git_worktree, self.src)
293 if os.path.exists(absSrc):
294 # Entity exists so just a simple one to one link operation
295 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
296 else:
297 # Entity doesn't exist assume there is a wild card
298 absDestDir = self.abs_dest
299 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
300 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700301 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700302 else:
303 absSrcFiles = glob.glob(absSrc)
304 for absSrcFile in absSrcFiles:
305 # Create a releative path from source dir to destination dir
306 absSrcDir = os.path.dirname(absSrcFile)
307 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
308
309 # Get the source file name
310 srcFile = os.path.basename(absSrcFile)
311
312 # Now form the final full paths to srcFile. They will be
313 # absolute for the desintaiton and relative for the srouce.
314 absDest = os.path.join(absDestDir, srcFile)
315 relSrc = os.path.join(relSrcDir, srcFile)
316 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500317
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700318
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700319class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700320
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700321 def __init__(self,
322 name,
Anthony King7bdac712014-07-16 12:56:40 +0100323 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700324 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100325 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700326 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700327 orig_name=None,
328 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700329 self.name = name
330 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700331 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700332 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100333 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700334 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700335 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700336
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700337
Doug Anderson37282b42011-03-04 11:54:18 -0800338class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700339
Doug Anderson37282b42011-03-04 11:54:18 -0800340 """A RepoHook contains information about a script to run as a hook.
341
342 Hooks are used to run a python script before running an upload (for instance,
343 to run presubmit checks). Eventually, we may have hooks for other actions.
344
345 This shouldn't be confused with files in the 'repo/hooks' directory. Those
346 files are copied into each '.git/hooks' folder for each project. Repo-level
347 hooks are associated instead with repo actions.
348
349 Hooks are always python. When a hook is run, we will load the hook into the
350 interpreter and execute its main() function.
351 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Doug Anderson37282b42011-03-04 11:54:18 -0800353 def __init__(self,
354 hook_type,
355 hooks_project,
356 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400357 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800358 abort_if_user_denies=False):
359 """RepoHook constructor.
360
361 Params:
362 hook_type: A string representing the type of hook. This is also used
363 to figure out the name of the file containing the hook. For
364 example: 'pre-upload'.
365 hooks_project: The project containing the repo hooks. If you have a
366 manifest, this is manifest.repo_hooks_project. OK if this is None,
367 which will make the hook a no-op.
368 topdir: Repo's top directory (the one containing the .repo directory).
369 Scripts will run with CWD as this directory. If you have a manifest,
370 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400371 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800372 abort_if_user_denies: If True, we'll throw a HookError() if the user
373 doesn't allow us to run the hook.
374 """
375 self._hook_type = hook_type
376 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400377 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800378 self._topdir = topdir
379 self._abort_if_user_denies = abort_if_user_denies
380
381 # Store the full path to the script for convenience.
382 if self._hooks_project:
383 self._script_fullpath = os.path.join(self._hooks_project.worktree,
384 self._hook_type + '.py')
385 else:
386 self._script_fullpath = None
387
388 def _GetHash(self):
389 """Return a hash of the contents of the hooks directory.
390
391 We'll just use git to do this. This hash has the property that if anything
392 changes in the directory we will return a different has.
393
394 SECURITY CONSIDERATION:
395 This hash only represents the contents of files in the hook directory, not
396 any other files imported or called by hooks. Changes to imported files
397 can change the script behavior without affecting the hash.
398
399 Returns:
400 A string representing the hash. This will always be ASCII so that it can
401 be printed to the user easily.
402 """
403 assert self._hooks_project, "Must have hooks to calculate their hash."
404
405 # We will use the work_git object rather than just calling GetRevisionId().
406 # That gives us a hash of the latest checked in version of the files that
407 # the user will actually be executing. Specifically, GetRevisionId()
408 # doesn't appear to change even if a user checks out a different version
409 # of the hooks repo (via git checkout) nor if a user commits their own revs.
410 #
411 # NOTE: Local (non-committed) changes will not be factored into this hash.
412 # I think this is OK, since we're really only worried about warning the user
413 # about upstream changes.
414 return self._hooks_project.work_git.rev_parse('HEAD')
415
416 def _GetMustVerb(self):
417 """Return 'must' if the hook is required; 'should' if not."""
418 if self._abort_if_user_denies:
419 return 'must'
420 else:
421 return 'should'
422
423 def _CheckForHookApproval(self):
424 """Check to see whether this hook has been approved.
425
Mike Frysinger40252c22016-08-15 21:23:44 -0400426 We'll accept approval of manifest URLs if they're using secure transports.
427 This way the user can say they trust the manifest hoster. For insecure
428 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800429
430 Note that we ask permission for each individual hook even though we use
431 the hash of all hooks when detecting changes. We'd like the user to be
432 able to approve / deny each hook individually. We only use the hash of all
433 hooks because there is no other easy way to detect changes to local imports.
434
435 Returns:
436 True if this hook is approved to run; False otherwise.
437
438 Raises:
439 HookError: Raised if the user doesn't approve and abort_if_user_denies
440 was passed to the consturctor.
441 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400442 if self._ManifestUrlHasSecureScheme():
443 return self._CheckForHookApprovalManifest()
444 else:
445 return self._CheckForHookApprovalHash()
446
447 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
448 changed_prompt):
449 """Check for approval for a particular attribute and hook.
450
451 Args:
452 subkey: The git config key under [repo.hooks.<hook_type>] to store the
453 last approved string.
454 new_val: The new value to compare against the last approved one.
455 main_prompt: Message to display to the user to ask for approval.
456 changed_prompt: Message explaining why we're re-asking for approval.
457
458 Returns:
459 True if this hook is approved to run; False otherwise.
460
461 Raises:
462 HookError: Raised if the user doesn't approve and abort_if_user_denies
463 was passed to the consturctor.
464 """
Doug Anderson37282b42011-03-04 11:54:18 -0800465 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400466 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800467
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 # Get the last value that the user approved for this hook; may be None.
469 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800470
Mike Frysinger40252c22016-08-15 21:23:44 -0400471 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800472 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400473 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800474 # Approval matched. We're done.
475 return True
476 else:
477 # Give the user a reason why we're prompting, since they last told
478 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400479 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800480 else:
481 prompt = ''
482
483 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
484 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400485 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530486 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900487 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800488
489 # User is doing a one-time approval.
490 if response in ('y', 'yes'):
491 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400492 elif response == 'always':
493 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800494 return True
495
496 # For anything else, we'll assume no approval.
497 if self._abort_if_user_denies:
498 raise HookError('You must allow the %s hook or use --no-verify.' %
499 self._hook_type)
500
501 return False
502
Mike Frysinger40252c22016-08-15 21:23:44 -0400503 def _ManifestUrlHasSecureScheme(self):
504 """Check if the URI for the manifest is a secure transport."""
505 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
506 parse_results = urllib.parse.urlparse(self._manifest_url)
507 return parse_results.scheme in secure_schemes
508
509 def _CheckForHookApprovalManifest(self):
510 """Check whether the user has approved this manifest host.
511
512 Returns:
513 True if this hook is approved to run; False otherwise.
514 """
515 return self._CheckForHookApprovalHelper(
516 'approvedmanifest',
517 self._manifest_url,
518 'Run hook scripts from %s' % (self._manifest_url,),
519 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
520
521 def _CheckForHookApprovalHash(self):
522 """Check whether the user has approved the hooks repo.
523
524 Returns:
525 True if this hook is approved to run; False otherwise.
526 """
527 prompt = ('Repo %s run the script:\n'
528 ' %s\n'
529 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700530 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400531 return self._CheckForHookApprovalHelper(
532 'approvedhash',
533 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700534 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400535 'Scripts have changed since %s was allowed.' % (self._hook_type,))
536
Doug Anderson37282b42011-03-04 11:54:18 -0800537 def _ExecuteHook(self, **kwargs):
538 """Actually execute the given hook.
539
540 This will run the hook's 'main' function in our python interpreter.
541
542 Args:
543 kwargs: Keyword arguments to pass to the hook. These are often specific
544 to the hook type. For instance, pre-upload hooks will contain
545 a project_list.
546 """
547 # Keep sys.path and CWD stashed away so that we can always restore them
548 # upon function exit.
549 orig_path = os.getcwd()
550 orig_syspath = sys.path
551
552 try:
553 # Always run hooks with CWD as topdir.
554 os.chdir(self._topdir)
555
556 # Put the hook dir as the first item of sys.path so hooks can do
557 # relative imports. We want to replace the repo dir as [0] so
558 # hooks can't import repo files.
559 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
560
561 # Exec, storing global context in the context dict. We catch exceptions
562 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500563 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800564 try:
Anthony King70f68902014-05-05 21:15:34 +0100565 exec(compile(open(self._script_fullpath).read(),
566 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800567 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700568 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
569 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800570
571 # Running the script should have defined a main() function.
572 if 'main' not in context:
573 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
574
Doug Anderson37282b42011-03-04 11:54:18 -0800575 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
576 # We don't actually want hooks to define their main with this argument--
577 # it's there to remind them that their hook should always take **kwargs.
578 # For instance, a pre-upload hook should be defined like:
579 # def main(project_list, **kwargs):
580 #
581 # This allows us to later expand the API without breaking old hooks.
582 kwargs = kwargs.copy()
583 kwargs['hook_should_take_kwargs'] = True
584
585 # Call the main function in the hook. If the hook should cause the
586 # build to fail, it will raise an Exception. We'll catch that convert
587 # to a HookError w/ just the failing traceback.
588 try:
589 context['main'](**kwargs)
590 except Exception:
591 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700592 'above.' % (traceback.format_exc(),
593 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800594 finally:
595 # Restore sys.path and CWD.
596 sys.path = orig_syspath
597 os.chdir(orig_path)
598
599 def Run(self, user_allows_all_hooks, **kwargs):
600 """Run the hook.
601
602 If the hook doesn't exist (because there is no hooks project or because
603 this particular hook is not enabled), this is a no-op.
604
605 Args:
606 user_allows_all_hooks: If True, we will never prompt about running the
607 hook--we'll just assume it's OK to run it.
608 kwargs: Keyword arguments to pass to the hook. These are often specific
609 to the hook type. For instance, pre-upload hooks will contain
610 a project_list.
611
612 Raises:
613 HookError: If there was a problem finding the hook or the user declined
614 to run a required hook (from _CheckForHookApproval).
615 """
616 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700617 if ((not self._hooks_project) or (self._hook_type not in
618 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800619 return
620
621 # Bail with a nice error if we can't find the hook.
622 if not os.path.isfile(self._script_fullpath):
623 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
624
625 # Make sure the user is OK with running the hook.
626 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
627 return
628
629 # Run the hook with the same version of python we're using.
630 self._ExecuteHook(**kwargs)
631
632
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600634 # These objects can be shared between several working trees.
635 shareable_files = ['description', 'info']
636 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
637 # These objects can only be used by a single working tree.
638 working_tree_files = ['config', 'packed-refs', 'shallow']
639 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700640
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641 def __init__(self,
642 manifest,
643 name,
644 remote,
645 gitdir,
David James8d201162013-10-11 17:03:19 -0700646 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 worktree,
648 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700649 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800650 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100651 rebase=True,
652 groups=None,
653 sync_c=False,
654 sync_s=False,
655 clone_depth=None,
656 upstream=None,
657 parent=None,
658 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900659 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700660 optimized_fetch=False,
661 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800662 """Init a Project object.
663
664 Args:
665 manifest: The XmlManifest object.
666 name: The `name` attribute of manifest.xml's project element.
667 remote: RemoteSpec object specifying its remote's properties.
668 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700669 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800670 worktree: Absolute path of git working tree.
671 relpath: Relative path of git working tree to repo's top directory.
672 revisionExpr: The `revision` attribute of manifest.xml's project element.
673 revisionId: git commit id for checking out.
674 rebase: The `rebase` attribute of manifest.xml's project element.
675 groups: The `groups` attribute of manifest.xml's project element.
676 sync_c: The `sync-c` attribute of manifest.xml's project element.
677 sync_s: The `sync-s` attribute of manifest.xml's project element.
678 upstream: The `upstream` attribute of manifest.xml's project element.
679 parent: The parent Project object.
680 is_derived: False if the project was explicitly defined in the manifest;
681 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400682 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900683 optimized_fetch: If True, when a project is set to a sha1 revision, only
684 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700685 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800686 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700687 self.manifest = manifest
688 self.name = name
689 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800690 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700691 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800692 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700693 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800694 else:
695 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700697 self.revisionExpr = revisionExpr
698
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700699 if revisionId is None \
700 and revisionExpr \
701 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700702 self.revisionId = revisionExpr
703 else:
704 self.revisionId = revisionId
705
Mike Pontillod3153822012-02-28 11:53:24 -0800706 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700707 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700708 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800709 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900710 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700711 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800712 self.parent = parent
713 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900714 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800715 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800716
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500719 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500720 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700721 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
722 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800724 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700725 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800726 else:
727 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700728 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700729 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700730 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400731 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700732 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733
Doug Anderson37282b42011-03-04 11:54:18 -0800734 # This will be filled in if a project is later identified to be the
735 # project containing repo hooks.
736 self.enabled_repo_hooks = []
737
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800739 def Derived(self):
740 return self.is_derived
741
742 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600744 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745
746 @property
747 def CurrentBranch(self):
748 """Obtain the name of the currently checked out branch.
749 The branch name omits the 'refs/heads/' prefix.
750 None is returned if the project is on a detached HEAD.
751 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700752 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753 if b.startswith(R_HEADS):
754 return b[len(R_HEADS):]
755 return None
756
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700757 def IsRebaseInProgress(self):
758 w = self.worktree
759 g = os.path.join(w, '.git')
760 return os.path.exists(os.path.join(g, 'rebase-apply')) \
761 or os.path.exists(os.path.join(g, 'rebase-merge')) \
762 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 def IsDirty(self, consider_untracked=True):
765 """Is the working directory modified in some way?
766 """
767 self.work_git.update_index('-q',
768 '--unmerged',
769 '--ignore-missing',
770 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900771 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772 return True
773 if self.work_git.DiffZ('diff-files'):
774 return True
775 if consider_untracked and self.work_git.LsOthers():
776 return True
777 return False
778
779 _userident_name = None
780 _userident_email = None
781
782 @property
783 def UserName(self):
784 """Obtain the user's personal name.
785 """
786 if self._userident_name is None:
787 self._LoadUserIdentity()
788 return self._userident_name
789
790 @property
791 def UserEmail(self):
792 """Obtain the user's email address. This is very likely
793 to be their Gerrit login.
794 """
795 if self._userident_email is None:
796 self._LoadUserIdentity()
797 return self._userident_email
798
799 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900800 u = self.bare_git.var('GIT_COMMITTER_IDENT')
801 m = re.compile("^(.*) <([^>]*)> ").match(u)
802 if m:
803 self._userident_name = m.group(1)
804 self._userident_email = m.group(2)
805 else:
806 self._userident_name = ''
807 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808
809 def GetRemote(self, name):
810 """Get the configuration for a single remote.
811 """
812 return self.config.GetRemote(name)
813
814 def GetBranch(self, name):
815 """Get the configuration for a single branch.
816 """
817 return self.config.GetBranch(name)
818
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700819 def GetBranches(self):
820 """Get all existing local branches.
821 """
822 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900823 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700824 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700825
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530826 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700827 if name.startswith(R_HEADS):
828 name = name[len(R_HEADS):]
829 b = self.GetBranch(name)
830 b.current = name == current
831 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900832 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700833 heads[name] = b
834
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530835 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700836 if name.startswith(R_PUB):
837 name = name[len(R_PUB):]
838 b = heads.get(name)
839 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900840 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700841
842 return heads
843
Colin Cross5acde752012-03-28 20:15:45 -0700844 def MatchesGroups(self, manifest_groups):
845 """Returns true if the manifest groups specified at init should cause
846 this project to be synced.
847 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700848 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700849
850 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700851 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700852 manifest_groups: "-group1,group2"
853 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500854
855 The special manifest group "default" will match any project that
856 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700857 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500858 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700859 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700860 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500861 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700862
Conley Owens971de8e2012-04-16 10:36:08 -0700863 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700864 for group in expanded_manifest_groups:
865 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700866 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700867 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700868 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700869
Conley Owens971de8e2012-04-16 10:36:08 -0700870 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700872# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700873 def UncommitedFiles(self, get_all=True):
874 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700876 Args:
877 get_all: a boolean, if True - get information about all different
878 uncommitted files. If False - return as soon as any kind of
879 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500880 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700881 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500882 self.work_git.update_index('-q',
883 '--unmerged',
884 '--ignore-missing',
885 '--refresh')
886 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700887 details.append("rebase in progress")
888 if not get_all:
889 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500890
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700891 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
892 if changes:
893 details.extend(changes)
894 if not get_all:
895 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500896
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700897 changes = self.work_git.DiffZ('diff-files').keys()
898 if changes:
899 details.extend(changes)
900 if not get_all:
901 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500902
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700903 changes = self.work_git.LsOthers()
904 if changes:
905 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500906
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700907 return details
908
909 def HasChanges(self):
910 """Returns true if there are uncommitted changes.
911 """
912 if self.UncommitedFiles(get_all=False):
913 return True
914 else:
915 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500916
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600917 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200919
920 Args:
921 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600922 quiet: If True then only print the project name. Do not print
923 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """
925 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700926 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200927 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700928 print(file=output_redir)
929 print('project %s/' % self.relpath, file=output_redir)
930 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 return
932
933 self.work_git.update_index('-q',
934 '--unmerged',
935 '--ignore-missing',
936 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700937 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
939 df = self.work_git.DiffZ('diff-files')
940 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100941 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700942 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
944 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700945 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200946 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700947 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600949 if quiet:
950 out.nl()
951 return 'DIRTY'
952
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 branch = self.CurrentBranch
954 if branch is None:
955 out.nobranch('(*** NO BRANCH ***)')
956 else:
957 out.branch('branch %s', branch)
958 out.nl()
959
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700960 if rb:
961 out.important('prior sync failed; rebase still in progress')
962 out.nl()
963
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 paths = list()
965 paths.extend(di.keys())
966 paths.extend(df.keys())
967 paths.extend(do)
968
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530969 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900970 try:
971 i = di[p]
972 except KeyError:
973 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900975 try:
976 f = df[p]
977 except KeyError:
978 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200979
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900980 if i:
981 i_status = i.status.upper()
982 else:
983 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900985 if f:
986 f_status = f.status.lower()
987 else:
988 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989
990 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800991 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700992 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993 else:
994 line = ' %s%s\t%s' % (i_status, f_status, p)
995
996 if i and not f:
997 out.added('%s', line)
998 elif (i and f) or (not i and f):
999 out.changed('%s', line)
1000 elif not i and not f:
1001 out.untracked('%s', line)
1002 else:
1003 out.write('%s', line)
1004 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001005
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001006 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007
pelyad67872d2012-03-28 14:49:58 +03001008 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 """Prints the status of the repository to stdout.
1010 """
1011 out = DiffColoring(self.config)
1012 cmd = ['diff']
1013 if out.is_on:
1014 cmd.append('--color')
1015 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001016 if absolute_paths:
1017 cmd.append('--src-prefix=a/%s/' % self.relpath)
1018 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 cmd.append('--')
1020 p = GitCommand(self,
1021 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001022 capture_stdout=True,
1023 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 has_diff = False
1025 for line in p.process.stdout:
1026 if not has_diff:
1027 out.nl()
1028 out.project('project %s/' % self.relpath)
1029 out.nl()
1030 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001031 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 p.Wait()
1033
1034
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001035# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
David Pursehouse8a68ff92012-09-24 12:15:13 +09001037 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 """Was the branch published (uploaded) for code review?
1039 If so, returns the SHA-1 hash of the last published
1040 state for the branch.
1041 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001042 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001044 try:
1045 return self.bare_git.rev_parse(key)
1046 except GitError:
1047 return None
1048 else:
1049 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001050 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001051 except KeyError:
1052 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053
David Pursehouse8a68ff92012-09-24 12:15:13 +09001054 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 """Prunes any stale published refs.
1056 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001057 if all_refs is None:
1058 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 heads = set()
1060 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301061 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 if name.startswith(R_HEADS):
1063 heads.add(name)
1064 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001065 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301067 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 n = name[len(R_PUB):]
1069 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001070 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001072 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 """List any branches which can be uploaded for review.
1074 """
1075 heads = {}
1076 pubed = {}
1077
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301078 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001080 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001082 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083
1084 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301085 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001086 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001088 if selected_branch and branch != selected_branch:
1089 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001091 rb = self.GetUploadableBranch(branch)
1092 if rb:
1093 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094 return ready
1095
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001096 def GetUploadableBranch(self, branch_name):
1097 """Get a single uploadable branch, or None.
1098 """
1099 branch = self.GetBranch(branch_name)
1100 base = branch.LocalMerge
1101 if branch.LocalMerge:
1102 rb = ReviewableBranch(self, branch, base)
1103 if rb.commits:
1104 return rb
1105 return None
1106
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001107 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001108 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001109 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001110 draft=False,
1111 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 """Uploads the named branch for code review.
1113 """
1114 if branch is None:
1115 branch = self.CurrentBranch
1116 if branch is None:
1117 raise GitError('not currently on a branch')
1118
1119 branch = self.GetBranch(branch)
1120 if not branch.LocalMerge:
1121 raise GitError('branch %s does not track a remote' % branch.name)
1122 if not branch.remote.review:
1123 raise GitError('remote %s has no review url' % branch.remote.name)
1124
Bryan Jacobsf609f912013-05-06 13:36:24 -04001125 if dest_branch is None:
1126 dest_branch = self.dest_branch
1127 if dest_branch is None:
1128 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 if not dest_branch.startswith(R_HEADS):
1130 dest_branch = R_HEADS + dest_branch
1131
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001132 if not branch.remote.projectname:
1133 branch.remote.projectname = self.name
1134 branch.remote.Save()
1135
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001136 url = branch.remote.ReviewUrl(self.UserEmail)
1137 if url is None:
1138 raise UploadError('review not configured')
1139 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001140
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001141 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001142 rp = ['gerrit receive-pack']
1143 for e in people[0]:
1144 rp.append('--reviewer=%s' % sq(e))
1145 for e in people[1]:
1146 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001147 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001148
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001149 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001150
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001151 if dest_branch.startswith(R_HEADS):
1152 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001153
1154 upload_type = 'for'
1155 if draft:
1156 upload_type = 'drafts'
1157
1158 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1159 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001160 if auto_topic:
1161 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001162 if not url.startswith('ssh://'):
1163 rp = ['r=%s' % p for p in people[0]] + \
1164 ['cc=%s' % p for p in people[1]]
1165 if rp:
1166 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001167 cmd.append(ref_spec)
1168
Anthony King7bdac712014-07-16 12:56:40 +01001169 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001170 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171
1172 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1173 self.bare_git.UpdateRef(R_PUB + branch.name,
1174 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001175 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
1177
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001178# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
Julien Campergue335f5ef2013-10-16 11:02:35 +02001180 def _ExtractArchive(self, tarpath, path=None):
1181 """Extract the given tar on its current location
1182
1183 Args:
1184 - tarpath: The path to the actual tar file
1185
1186 """
1187 try:
1188 with tarfile.open(tarpath, 'r') as tar:
1189 tar.extractall(path=path)
1190 return True
1191 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001192 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001193 return False
1194
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001195 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001196 quiet=False,
1197 is_new=None,
1198 current_branch_only=False,
1199 force_sync=False,
1200 clone_bundle=True,
1201 no_tags=False,
1202 archive=False,
1203 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001204 prune=False,
1205 submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206 """Perform only the network IO portion of the sync process.
1207 Local working directory/branch state is not affected.
1208 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001209 if archive and not isinstance(self, MetaProject):
1210 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001211 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001212 return False
1213
1214 name = self.relpath.replace('\\', '/')
1215 name = name.replace('/', '_')
1216 tarpath = '%s.tar' % name
1217 topdir = self.manifest.topdir
1218
1219 try:
1220 self._FetchArchive(tarpath, cwd=topdir)
1221 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001222 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001223 return False
1224
1225 # From now on, we only need absolute tarpath
1226 tarpath = os.path.join(topdir, tarpath)
1227
1228 if not self._ExtractArchive(tarpath, path=topdir):
1229 return False
1230 try:
1231 os.remove(tarpath)
1232 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001233 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001234 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001235 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001236 if is_new is None:
1237 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001238 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001239 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001240 else:
1241 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001243
1244 if is_new:
1245 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1246 try:
1247 fd = open(alt, 'rb')
1248 try:
1249 alt_dir = fd.readline().rstrip()
1250 finally:
1251 fd.close()
1252 except IOError:
1253 alt_dir = None
1254 else:
1255 alt_dir = None
1256
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001257 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001258 and alt_dir is None \
1259 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001260 is_new = False
1261
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001262 if not current_branch_only:
1263 if self.sync_c:
1264 current_branch_only = True
1265 elif not self.manifest._loaded:
1266 # Manifest cannot check defaults until it syncs.
1267 current_branch_only = False
1268 elif self.manifest.default.sync_c:
1269 current_branch_only = True
1270
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001271 if self.clone_depth:
1272 depth = self.clone_depth
1273 else:
1274 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1275
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001276 need_to_fetch = not (optimized_fetch and
1277 (ID_RE.match(self.revisionExpr) and
1278 self._CheckForSha1()))
1279 if (need_to_fetch and
1280 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1281 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001282 no_tags=no_tags, prune=prune, depth=depth,
1283 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001284 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001285
1286 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001287 self._InitMRef()
1288 else:
1289 self._InitMirrorHead()
1290 try:
1291 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1292 except OSError:
1293 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001295
1296 def PostRepoUpgrade(self):
1297 self._InitHooks()
1298
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001299 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001300 if self.manifest.isGitcClient:
1301 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 for copyfile in self.copyfiles:
1303 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001304 for linkfile in self.linkfiles:
1305 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306
Julien Camperguedd654222014-01-09 16:21:37 +01001307 def GetCommitRevisionId(self):
1308 """Get revisionId of a commit.
1309
1310 Use this method instead of GetRevisionId to get the id of the commit rather
1311 than the id of the current git object (for example, a tag)
1312
1313 """
1314 if not self.revisionExpr.startswith(R_TAGS):
1315 return self.GetRevisionId(self._allrefs)
1316
1317 try:
1318 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1319 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001320 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1321 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001322
David Pursehouse8a68ff92012-09-24 12:15:13 +09001323 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001324 if self.revisionId:
1325 return self.revisionId
1326
1327 rem = self.GetRemote(self.remote.name)
1328 rev = rem.ToLocal(self.revisionExpr)
1329
David Pursehouse8a68ff92012-09-24 12:15:13 +09001330 if all_refs is not None and rev in all_refs:
1331 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332
1333 try:
1334 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1335 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001336 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1337 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338
Martin Kellye4e94d22017-03-21 16:05:12 -07001339 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 """Perform only the local IO portion of the sync process.
1341 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001343 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001344 all_refs = self.bare_ref.all
1345 self.CleanPublishedCache(all_refs)
1346 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001347
David Pursehouse1d947b32012-10-25 12:23:11 +09001348 def _doff():
1349 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001350 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001351
Martin Kellye4e94d22017-03-21 16:05:12 -07001352 def _dosubmodules():
1353 self._SyncSubmodules(quiet=True)
1354
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001355 head = self.work_git.GetHead()
1356 if head.startswith(R_HEADS):
1357 branch = head[len(R_HEADS):]
1358 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001359 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001360 except KeyError:
1361 head = None
1362 else:
1363 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 # Currently on a detached HEAD. The user is assumed to
1367 # not have any local modifications worth worrying about.
1368 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001369 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001370 syncbuf.fail(self, _PriorSyncFailedError())
1371 return
1372
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001373 if head == revid:
1374 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001375 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001376 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001377 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001378 # The copy/linkfile config may have changed.
1379 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001380 return
1381 else:
1382 lost = self._revlist(not_rev(revid), HEAD)
1383 if lost:
1384 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001387 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001388 if submodules:
1389 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001390 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.fail(self, e)
1392 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001393 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001396 if head == revid:
1397 # No changes; don't do anything further.
1398 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001399 # The copy/linkfile config may have changed.
1400 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001401 return
1402
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001407 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001409 syncbuf.info(self,
1410 "leaving %s; does not track upstream",
1411 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001413 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001414 if submodules:
1415 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001416 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 syncbuf.fail(self, e)
1418 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001419 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001420 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001422 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001423 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001425 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426 if not_merged:
1427 if upstream_gain:
1428 # The user has published this branch and some of those
1429 # commits are not yet merged upstream. We do not want
1430 # to rewrite the published commits so we punt.
1431 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001432 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001433 "branch %s is published (but not merged) and is now "
1434 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001435 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001436 elif pub == head:
1437 # All published commits are merged, and thus we are a
1438 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001439 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001440 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001441 if submodules:
1442 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001443 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001444
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001445 # Examine the local commits not in the remote. Find the
1446 # last one attributed to this user, if any.
1447 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001448 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001449 last_mine = None
1450 cnt_mine = 0
1451 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301452 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001453 if committer_email == self.UserEmail:
1454 last_mine = commit_id
1455 cnt_mine += 1
1456
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001457 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001458 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459
1460 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001461 syncbuf.fail(self, _DirtyError())
1462 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001464 # If the upstream switched on us, warn the user.
1465 #
1466 if branch.merge != self.revisionExpr:
1467 if branch.merge and self.revisionExpr:
1468 syncbuf.info(self,
1469 'manifest switched %s...%s',
1470 branch.merge,
1471 self.revisionExpr)
1472 elif branch.merge:
1473 syncbuf.info(self,
1474 'manifest no longer tracks %s',
1475 branch.merge)
1476
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001477 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001479 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001481 syncbuf.info(self,
1482 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001483 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001484
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001485 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001486 if not ID_RE.match(self.revisionExpr):
1487 # in case of manifest sync the revisionExpr might be a SHA1
1488 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001489 if not branch.merge.startswith('refs/'):
1490 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 branch.Save()
1492
Mike Pontillod3153822012-02-28 11:53:24 -08001493 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001494 def _docopyandlink():
1495 self._CopyAndLinkFiles()
1496
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001497 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001498 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001499 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001500 if submodules:
1501 syncbuf.later2(self, _dosubmodules)
1502 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001503 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001505 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001506 if submodules:
1507 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001508 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001509 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001510 syncbuf.fail(self, e)
1511 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001513 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001514 if submodules:
1515 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001516
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001517 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001518 # dest should already be an absolute path, but src is project relative
1519 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001520 abssrc = os.path.join(self.worktree, src)
1521 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001523 def AddLinkFile(self, src, dest, absdest):
1524 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001525 # make src relative path to dest
1526 absdestdir = os.path.dirname(absdest)
1527 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001528 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001529
James W. Mills24c13082012-04-12 15:04:13 -05001530 def AddAnnotation(self, name, value, keep):
1531 self.annotations.append(_Annotation(name, value, keep))
1532
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001533 def DownloadPatchSet(self, change_id, patch_id):
1534 """Download a single patch set of a single change to FETCH_HEAD.
1535 """
1536 remote = self.GetRemote(self.remote.name)
1537
1538 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001539 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001540 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001541 if GitCommand(self, cmd, bare=True).Wait() != 0:
1542 return None
1543 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001544 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001545 change_id,
1546 patch_id,
1547 self.bare_git.rev_parse('FETCH_HEAD'))
1548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001549
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001550# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551
Simran Basib9a1b732015-08-20 12:19:28 -07001552 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553 """Create a new branch off the manifest's revision.
1554 """
Simran Basib9a1b732015-08-20 12:19:28 -07001555 if not branch_merge:
1556 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001557 head = self.work_git.GetHead()
1558 if head == (R_HEADS + name):
1559 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560
David Pursehouse8a68ff92012-09-24 12:15:13 +09001561 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001562 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001563 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001564 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001565 capture_stdout=True,
1566 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001567
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001568 branch = self.GetBranch(name)
1569 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001570 branch.merge = branch_merge
1571 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1572 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001573 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001574
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001575 if head.startswith(R_HEADS):
1576 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001577 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001578 except KeyError:
1579 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001580 if revid and head and revid == head:
1581 ref = os.path.join(self.gitdir, R_HEADS + name)
1582 try:
1583 os.makedirs(os.path.dirname(ref))
1584 except OSError:
1585 pass
1586 _lwrite(ref, '%s\n' % revid)
1587 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1588 'ref: %s%s\n' % (R_HEADS, name))
1589 branch.Save()
1590 return True
1591
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001592 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001593 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001594 capture_stdout=True,
1595 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001596 branch.Save()
1597 return True
1598 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599
Wink Saville02d79452009-04-10 13:01:24 -07001600 def CheckoutBranch(self, name):
1601 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001602
1603 Args:
1604 name: The name of the branch to checkout.
1605
1606 Returns:
1607 True if the checkout succeeded; False if it didn't; None if the branch
1608 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001609 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001610 rev = R_HEADS + name
1611 head = self.work_git.GetHead()
1612 if head == rev:
1613 # Already on the branch
1614 #
1615 return True
Wink Saville02d79452009-04-10 13:01:24 -07001616
David Pursehouse8a68ff92012-09-24 12:15:13 +09001617 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001618 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001619 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001620 except KeyError:
1621 # Branch does not exist in this project
1622 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001623 return None
Wink Saville02d79452009-04-10 13:01:24 -07001624
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001625 if head.startswith(R_HEADS):
1626 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001627 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001628 except KeyError:
1629 head = None
1630
1631 if head == revid:
1632 # Same revision; just update HEAD to point to the new
1633 # target branch, but otherwise take no other action.
1634 #
1635 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1636 'ref: %s%s\n' % (R_HEADS, name))
1637 return True
1638
1639 return GitCommand(self,
1640 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001641 capture_stdout=True,
1642 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001643
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001644 def AbandonBranch(self, name):
1645 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001646
1647 Args:
1648 name: The name of the branch to abandon.
1649
1650 Returns:
1651 True if the abandon succeeded; False if it didn't; None if the branch
1652 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001653 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001654 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001655 all_refs = self.bare_ref.all
1656 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001657 # Doesn't exist
1658 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001659
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001660 head = self.work_git.GetHead()
1661 if head == rev:
1662 # We can't destroy the branch while we are sitting
1663 # on it. Switch to a detached HEAD.
1664 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001665 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001666
David Pursehouse8a68ff92012-09-24 12:15:13 +09001667 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001668 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001669 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1670 '%s\n' % revid)
1671 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001672 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001673
1674 return GitCommand(self,
1675 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001676 capture_stdout=True,
1677 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001678
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001679 def PruneHeads(self):
1680 """Prune any topic branches already merged into upstream.
1681 """
1682 cb = self.CurrentBranch
1683 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001684 left = self._allrefs
1685 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686 if name.startswith(R_HEADS):
1687 name = name[len(R_HEADS):]
1688 if cb is None or name != cb:
1689 kill.append(name)
1690
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001691 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 if cb is not None \
1693 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001694 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695 self.work_git.DetachHead(HEAD)
1696 kill.append(cb)
1697
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001699 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 try:
1702 self.bare_git.DetachHead(rev)
1703
1704 b = ['branch', '-d']
1705 b.extend(kill)
1706 b = GitCommand(self, b, bare=True,
1707 capture_stdout=True,
1708 capture_stderr=True)
1709 b.Wait()
1710 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001711 if ID_RE.match(old):
1712 self.bare_git.DetachHead(old)
1713 else:
1714 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001715 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001716
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001717 for branch in kill:
1718 if (R_HEADS + branch) not in left:
1719 self.CleanPublishedCache()
1720 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001721
1722 if cb and cb not in kill:
1723 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001724 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725
1726 kept = []
1727 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001728 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729 branch = self.GetBranch(branch)
1730 base = branch.LocalMerge
1731 if not base:
1732 base = rev
1733 kept.append(ReviewableBranch(self, branch, base))
1734 return kept
1735
1736
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001737# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001738
1739 def GetRegisteredSubprojects(self):
1740 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001741
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001742 def rec(subprojects):
1743 if not subprojects:
1744 return
1745 result.extend(subprojects)
1746 for p in subprojects:
1747 rec(p.subprojects)
1748 rec(self.subprojects)
1749 return result
1750
1751 def _GetSubmodules(self):
1752 # Unfortunately we cannot call `git submodule status --recursive` here
1753 # because the working tree might not exist yet, and it cannot be used
1754 # without a working tree in its current implementation.
1755
1756 def get_submodules(gitdir, rev):
1757 # Parse .gitmodules for submodule sub_paths and sub_urls
1758 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1759 if not sub_paths:
1760 return []
1761 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1762 # revision of submodule repository
1763 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1764 submodules = []
1765 for sub_path, sub_url in zip(sub_paths, sub_urls):
1766 try:
1767 sub_rev = sub_revs[sub_path]
1768 except KeyError:
1769 # Ignore non-exist submodules
1770 continue
1771 submodules.append((sub_rev, sub_path, sub_url))
1772 return submodules
1773
1774 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1775 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001776
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001777 def parse_gitmodules(gitdir, rev):
1778 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1779 try:
Anthony King7bdac712014-07-16 12:56:40 +01001780 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1781 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001782 except GitError:
1783 return [], []
1784 if p.Wait() != 0:
1785 return [], []
1786
1787 gitmodules_lines = []
1788 fd, temp_gitmodules_path = tempfile.mkstemp()
1789 try:
1790 os.write(fd, p.stdout)
1791 os.close(fd)
1792 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001793 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1794 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001795 if p.Wait() != 0:
1796 return [], []
1797 gitmodules_lines = p.stdout.split('\n')
1798 except GitError:
1799 return [], []
1800 finally:
1801 os.remove(temp_gitmodules_path)
1802
1803 names = set()
1804 paths = {}
1805 urls = {}
1806 for line in gitmodules_lines:
1807 if not line:
1808 continue
1809 m = re_path.match(line)
1810 if m:
1811 names.add(m.group(1))
1812 paths[m.group(1)] = m.group(2)
1813 continue
1814 m = re_url.match(line)
1815 if m:
1816 names.add(m.group(1))
1817 urls[m.group(1)] = m.group(2)
1818 continue
1819 names = sorted(names)
1820 return ([paths.get(name, '') for name in names],
1821 [urls.get(name, '') for name in names])
1822
1823 def git_ls_tree(gitdir, rev, paths):
1824 cmd = ['ls-tree', rev, '--']
1825 cmd.extend(paths)
1826 try:
Anthony King7bdac712014-07-16 12:56:40 +01001827 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1828 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001829 except GitError:
1830 return []
1831 if p.Wait() != 0:
1832 return []
1833 objects = {}
1834 for line in p.stdout.split('\n'):
1835 if not line.strip():
1836 continue
1837 object_rev, object_path = line.split()[2:4]
1838 objects[object_path] = object_rev
1839 return objects
1840
1841 try:
1842 rev = self.GetRevisionId()
1843 except GitError:
1844 return []
1845 return get_submodules(self.gitdir, rev)
1846
1847 def GetDerivedSubprojects(self):
1848 result = []
1849 if not self.Exists:
1850 # If git repo does not exist yet, querying its submodules will
1851 # mess up its states; so return here.
1852 return result
1853 for rev, path, url in self._GetSubmodules():
1854 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001855 relpath, worktree, gitdir, objdir = \
1856 self.manifest.GetSubprojectPaths(self, name, path)
1857 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001858 if project:
1859 result.extend(project.GetDerivedSubprojects())
1860 continue
David James8d201162013-10-11 17:03:19 -07001861
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001862 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001863 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001864 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001865 review=self.remote.review,
1866 revision=self.remote.revision)
1867 subproject = Project(manifest=self.manifest,
1868 name=name,
1869 remote=remote,
1870 gitdir=gitdir,
1871 objdir=objdir,
1872 worktree=worktree,
1873 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001874 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001875 revisionId=rev,
1876 rebase=self.rebase,
1877 groups=self.groups,
1878 sync_c=self.sync_c,
1879 sync_s=self.sync_s,
1880 parent=self,
1881 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001882 result.append(subproject)
1883 result.extend(subproject.GetDerivedSubprojects())
1884 return result
1885
1886
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001887# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001888 def _CheckForSha1(self):
1889 try:
1890 # if revision (sha or tag) is not present then following function
1891 # throws an error.
1892 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1893 return True
1894 except GitError:
1895 # There is no such persistent revision. We have to fetch it.
1896 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001897
Julien Campergue335f5ef2013-10-16 11:02:35 +02001898 def _FetchArchive(self, tarpath, cwd=None):
1899 cmd = ['archive', '-v', '-o', tarpath]
1900 cmd.append('--remote=%s' % self.remote.url)
1901 cmd.append('--prefix=%s/' % self.relpath)
1902 cmd.append(self.revisionExpr)
1903
1904 command = GitCommand(self, cmd, cwd=cwd,
1905 capture_stdout=True,
1906 capture_stderr=True)
1907
1908 if command.Wait() != 0:
1909 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1910
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001911 def _RemoteFetch(self, name=None,
1912 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001913 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001914 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001915 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001916 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001917 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001918 depth=None,
1919 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001920
1921 is_sha1 = False
1922 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001923 # The depth should not be used when fetching to a mirror because
1924 # it will result in a shallow repository that cannot be cloned or
1925 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001926 # The repo project should also never be synced with partial depth.
1927 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1928 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001929
Shawn Pearce69e04d82014-01-29 12:48:54 -08001930 if depth:
1931 current_branch_only = True
1932
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001933 if ID_RE.match(self.revisionExpr) is not None:
1934 is_sha1 = True
1935
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001936 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001937 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001938 # this is a tag and its sha1 value should never change
1939 tag_name = self.revisionExpr[len(R_TAGS):]
1940
1941 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001942 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001943 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001944 if is_sha1 and not depth:
1945 # When syncing a specific commit and --depth is not set:
1946 # * if upstream is explicitly specified and is not a sha1, fetch only
1947 # upstream as users expect only upstream to be fetch.
1948 # Note: The commit might not be in upstream in which case the sync
1949 # will fail.
1950 # * otherwise, fetch all branches to make sure we end up with the
1951 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001952 if self.upstream:
1953 current_branch_only = not ID_RE.match(self.upstream)
1954 else:
1955 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001957 if not name:
1958 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001959
1960 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001961 remote = self.GetRemote(name)
1962 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001963 ssh_proxy = True
1964
Shawn O. Pearce88443382010-10-08 10:02:09 +02001965 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001966 if alt_dir and 'objects' == os.path.basename(alt_dir):
1967 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001968 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1969 remote = self.GetRemote(name)
1970
David Pursehouse8a68ff92012-09-24 12:15:13 +09001971 all_refs = self.bare_ref.all
1972 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001973 tmp = set()
1974
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301975 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001976 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001977 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001978 all_refs[r] = ref_id
1979 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001980 continue
1981
David Pursehouse8a68ff92012-09-24 12:15:13 +09001982 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001983 continue
1984
David Pursehouse8a68ff92012-09-24 12:15:13 +09001985 r = 'refs/_alt/%s' % ref_id
1986 all_refs[r] = ref_id
1987 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001988 tmp.add(r)
1989
heping3d7bbc92017-04-12 19:51:47 +08001990 tmp_packed_lines = []
1991 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02001992
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301993 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001994 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08001995 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001996 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08001997 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001998
heping3d7bbc92017-04-12 19:51:47 +08001999 tmp_packed = ''.join(tmp_packed_lines)
2000 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002001 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002002 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002003 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002004
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002005 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002006
Conley Owensf97e8382015-01-21 11:12:46 -08002007 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002008 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002009 else:
2010 # If this repo has shallow objects, then we don't know which refs have
2011 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2012 # do this with projects that don't have shallow objects, since it is less
2013 # efficient.
2014 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2015 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002016
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002017 if quiet:
2018 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002019 if not self.worktree:
2020 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002021 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002022
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002023 # If using depth then we should not get all the tags since they may
2024 # be outside of the depth.
2025 if no_tags or depth:
2026 cmd.append('--no-tags')
2027 else:
2028 cmd.append('--tags')
2029
David Pursehouse74cfd272015-10-14 10:50:15 +09002030 if prune:
2031 cmd.append('--prune')
2032
Martin Kellye4e94d22017-03-21 16:05:12 -07002033 if submodules:
2034 cmd.append('--recurse-submodules=on-demand')
2035
Conley Owens80b87fe2014-05-09 17:13:44 -07002036 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002037 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002038 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002039 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002040 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002041 spec.append('tag')
2042 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002043
David Pursehouse403b64e2015-04-27 10:41:33 +09002044 if not self.manifest.IsMirror:
2045 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002046 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002047 # Shallow checkout of a specific commit, fetch from that commit and not
2048 # the heads only as the commit might be deeper in the history.
2049 spec.append(branch)
2050 else:
2051 if is_sha1:
2052 branch = self.upstream
2053 if branch is not None and branch.strip():
2054 if not branch.startswith('refs/'):
2055 branch = R_HEADS + branch
2056 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002057 cmd.extend(spec)
2058
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002059 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002060 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002061 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002062 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002063 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002064 ok = True
2065 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002066 # If needed, run the 'git remote prune' the first time through the loop
2067 elif (not _i and
2068 "error:" in gitcmd.stderr and
2069 "git remote prune" in gitcmd.stderr):
2070 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002071 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002072 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002073 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002074 break
2075 continue
Brian Harring14a66742012-09-28 20:21:57 -07002076 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002077 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2078 # in sha1 mode, we just tried sync'ing from the upstream field; it
2079 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002080 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002081 elif ret < 0:
2082 # Git died with a signal, exit immediately
2083 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002084 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002085
2086 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002087 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002088 if old_packed != '':
2089 _lwrite(packed_refs, old_packed)
2090 else:
2091 os.remove(packed_refs)
2092 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002093
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002094 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002095 # We just synced the upstream given branch; verify we
2096 # got what we wanted, else trigger a second run of all
2097 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002098 if not self._CheckForSha1():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002099 if current_branch_only and depth:
2100 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002101 return self._RemoteFetch(name=name,
2102 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002103 initial=False, quiet=quiet, alt_dir=alt_dir,
2104 depth=None)
2105 else:
2106 # Avoid infinite recursion: sync all branches with depth set to None
2107 return self._RemoteFetch(name=name, current_branch_only=False,
2108 initial=False, quiet=quiet, alt_dir=alt_dir,
2109 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002110
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002111 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002112
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002113 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002114 if initial and \
2115 (self.manifest.manifestProject.config.GetString('repo.depth') or
2116 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002117 return False
2118
2119 remote = self.GetRemote(self.remote.name)
2120 bundle_url = remote.url + '/clone.bundle'
2121 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002122 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2123 'persistent-http',
2124 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002125 return False
2126
2127 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2128 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2129
2130 exist_dst = os.path.exists(bundle_dst)
2131 exist_tmp = os.path.exists(bundle_tmp)
2132
2133 if not initial and not exist_dst and not exist_tmp:
2134 return False
2135
2136 if not exist_dst:
2137 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2138 if not exist_dst:
2139 return False
2140
2141 cmd = ['fetch']
2142 if quiet:
2143 cmd.append('--quiet')
2144 if not self.worktree:
2145 cmd.append('--update-head-ok')
2146 cmd.append(bundle_dst)
2147 for f in remote.fetch:
2148 cmd.append(str(f))
2149 cmd.append('refs/tags/*:refs/tags/*')
2150
2151 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002152 if os.path.exists(bundle_dst):
2153 os.remove(bundle_dst)
2154 if os.path.exists(bundle_tmp):
2155 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002157
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002158 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002159 if os.path.exists(dstPath):
2160 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002161
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002162 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002163 if quiet:
2164 cmd += ['--silent']
2165 if os.path.exists(tmpPath):
2166 size = os.stat(tmpPath).st_size
2167 if size >= 1024:
2168 cmd += ['--continue-at', '%d' % (size,)]
2169 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002170 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002171 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2172 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002173 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002174 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002175 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002176 if srcUrl.startswith('persistent-'):
2177 srcUrl = srcUrl[len('persistent-'):]
2178 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002179
Dave Borowitz137d0132015-01-02 11:12:54 -08002180 if IsTrace():
2181 Trace('%s', ' '.join(cmd))
2182 try:
2183 proc = subprocess.Popen(cmd)
2184 except OSError:
2185 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002186
Dave Borowitz137d0132015-01-02 11:12:54 -08002187 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002188
Dave Borowitz137d0132015-01-02 11:12:54 -08002189 if curlret == 22:
2190 # From curl man page:
2191 # 22: HTTP page not retrieved. The requested url was not found or
2192 # returned another error with the HTTP error code being 400 or above.
2193 # This return code only appears if -f, --fail is used.
2194 if not quiet:
2195 print("Server does not provide clone.bundle; ignoring.",
2196 file=sys.stderr)
2197 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002198
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002199 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002200 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002201 os.rename(tmpPath, dstPath)
2202 return True
2203 else:
2204 os.remove(tmpPath)
2205 return False
2206 else:
2207 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002208
Kris Giesingc8d882a2014-12-23 13:02:32 -08002209 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002210 try:
2211 with open(path) as f:
2212 if f.read(16) == '# v2 git bundle\n':
2213 return True
2214 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002215 if not quiet:
2216 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002217 return False
2218 except OSError:
2219 return False
2220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221 def _Checkout(self, rev, quiet=False):
2222 cmd = ['checkout']
2223 if quiet:
2224 cmd.append('-q')
2225 cmd.append(rev)
2226 cmd.append('--')
2227 if GitCommand(self, cmd).Wait() != 0:
2228 if self._allrefs:
2229 raise GitError('%s checkout %s ' % (self.name, rev))
2230
Anthony King7bdac712014-07-16 12:56:40 +01002231 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002232 cmd = ['cherry-pick']
2233 cmd.append(rev)
2234 cmd.append('--')
2235 if GitCommand(self, cmd).Wait() != 0:
2236 if self._allrefs:
2237 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2238
Anthony King7bdac712014-07-16 12:56:40 +01002239 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002240 cmd = ['revert']
2241 cmd.append('--no-edit')
2242 cmd.append(rev)
2243 cmd.append('--')
2244 if GitCommand(self, cmd).Wait() != 0:
2245 if self._allrefs:
2246 raise GitError('%s revert %s ' % (self.name, rev))
2247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002248 def _ResetHard(self, rev, quiet=True):
2249 cmd = ['reset', '--hard']
2250 if quiet:
2251 cmd.append('-q')
2252 cmd.append(rev)
2253 if GitCommand(self, cmd).Wait() != 0:
2254 raise GitError('%s reset --hard %s ' % (self.name, rev))
2255
Martin Kellye4e94d22017-03-21 16:05:12 -07002256 def _SyncSubmodules(self, quiet=True):
2257 cmd = ['submodule', 'update', '--init', '--recursive']
2258 if quiet:
2259 cmd.append('-q')
2260 if GitCommand(self, cmd).Wait() != 0:
2261 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2262
Anthony King7bdac712014-07-16 12:56:40 +01002263 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002264 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002265 if onto is not None:
2266 cmd.extend(['--onto', onto])
2267 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002268 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002269 raise GitError('%s rebase %s ' % (self.name, upstream))
2270
Pierre Tardy3d125942012-05-04 12:18:12 +02002271 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002272 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002273 if ffonly:
2274 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002275 if GitCommand(self, cmd).Wait() != 0:
2276 raise GitError('%s merge %s ' % (self.name, head))
2277
Kevin Degiabaa7f32014-11-12 11:27:45 -07002278 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002279 init_git_dir = not os.path.exists(self.gitdir)
2280 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002281 try:
2282 # Initialize the bare repository, which contains all of the objects.
2283 if init_obj_dir:
2284 os.makedirs(self.objdir)
2285 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002286
Kevin Degib1a07b82015-07-27 13:33:43 -06002287 # If we have a separate directory to hold refs, initialize it as well.
2288 if self.objdir != self.gitdir:
2289 if init_git_dir:
2290 os.makedirs(self.gitdir)
2291
2292 if init_obj_dir or init_git_dir:
2293 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2294 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002295 try:
2296 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2297 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002298 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002299 print("Retrying clone after deleting %s" %
2300 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002301 try:
2302 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002303 if self.worktree and os.path.exists(os.path.realpath
2304 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002305 shutil.rmtree(os.path.realpath(self.worktree))
2306 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2307 except:
2308 raise e
2309 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002310
Kevin Degi384b3c52014-10-16 16:02:58 -06002311 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002312 mp = self.manifest.manifestProject
2313 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002314
Kevin Degib1a07b82015-07-27 13:33:43 -06002315 if ref_dir or mirror_git:
2316 if not mirror_git:
2317 mirror_git = os.path.join(ref_dir, self.name + '.git')
2318 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2319 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002320
Kevin Degib1a07b82015-07-27 13:33:43 -06002321 if os.path.exists(mirror_git):
2322 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002323
Kevin Degib1a07b82015-07-27 13:33:43 -06002324 elif os.path.exists(repo_git):
2325 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002326
Kevin Degib1a07b82015-07-27 13:33:43 -06002327 else:
2328 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002329
Kevin Degib1a07b82015-07-27 13:33:43 -06002330 if ref_dir:
2331 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2332 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002333
Kevin Degib1a07b82015-07-27 13:33:43 -06002334 self._UpdateHooks()
2335
2336 m = self.manifest.manifestProject.config
2337 for key in ['user.name', 'user.email']:
2338 if m.Has(key, include_defaults=False):
2339 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002340 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002341 if self.manifest.IsMirror:
2342 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002343 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002344 self.config.SetString('core.bare', None)
2345 except Exception:
2346 if init_obj_dir and os.path.exists(self.objdir):
2347 shutil.rmtree(self.objdir)
2348 if init_git_dir and os.path.exists(self.gitdir):
2349 shutil.rmtree(self.gitdir)
2350 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002351
Jimmie Westera0444582012-10-24 13:44:42 +02002352 def _UpdateHooks(self):
2353 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002354 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002355
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002356 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002357 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002358 if not os.path.exists(hooks):
2359 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002360 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002361 name = os.path.basename(stock_hook)
2362
Victor Boivie65e0f352011-04-18 11:23:29 +02002363 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002364 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002365 # Don't install a Gerrit Code Review hook if this
2366 # project does not appear to use it for reviews.
2367 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002368 # Since the manifest project is one of those, but also
2369 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002370 continue
2371
2372 dst = os.path.join(hooks, name)
2373 if os.path.islink(dst):
2374 continue
2375 if os.path.exists(dst):
2376 if filecmp.cmp(stock_hook, dst, shallow=False):
2377 os.remove(dst)
2378 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002379 _warn("%s: Not replacing locally modified %s hook",
2380 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002381 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002382 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002383 platform_utils.symlink(
2384 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002385 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002386 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002387 raise GitError('filesystem must support symlinks')
2388 else:
2389 raise
2390
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002392 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002394 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002395 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002396 remote.review = self.remote.review
2397 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002398
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002399 if self.worktree:
2400 remote.ResetFetch(mirror=False)
2401 else:
2402 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 remote.Save()
2404
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002405 def _InitMRef(self):
2406 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002407 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002408
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002409 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002410 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002411
2412 def _InitAnyMRef(self, ref):
2413 cur = self.bare_ref.symref(ref)
2414
2415 if self.revisionId:
2416 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2417 msg = 'manifest set to %s' % self.revisionId
2418 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002419 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002420 else:
2421 remote = self.GetRemote(self.remote.name)
2422 dst = remote.ToLocal(self.revisionExpr)
2423 if cur != dst:
2424 msg = 'manifest set to %s' % self.revisionExpr
2425 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002426
Kevin Degi384b3c52014-10-16 16:02:58 -06002427 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002428 symlink_files = self.shareable_files[:]
2429 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002430 if share_refs:
2431 symlink_files += self.working_tree_files
2432 symlink_dirs += self.working_tree_dirs
2433 to_symlink = symlink_files + symlink_dirs
2434 for name in set(to_symlink):
2435 dst = os.path.realpath(os.path.join(destdir, name))
2436 if os.path.lexists(dst):
2437 src = os.path.realpath(os.path.join(srcdir, name))
2438 # Fail if the links are pointing to the wrong place
2439 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002440 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002441 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002442 'work tree. If you\'re comfortable with the '
2443 'possibility of losing the work tree\'s git metadata,'
2444 ' use `repo sync --force-sync {0}` to '
2445 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002446
David James8d201162013-10-11 17:03:19 -07002447 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2448 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2449
2450 Args:
2451 gitdir: The bare git repository. Must already be initialized.
2452 dotgit: The repository you would like to initialize.
2453 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2454 Only one work tree can store refs under a given |gitdir|.
2455 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2456 This saves you the effort of initializing |dotgit| yourself.
2457 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002458 symlink_files = self.shareable_files[:]
2459 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002460 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002461 symlink_files += self.working_tree_files
2462 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002463 to_symlink = symlink_files + symlink_dirs
2464
2465 to_copy = []
2466 if copy_all:
2467 to_copy = os.listdir(gitdir)
2468
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002469 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002470 for name in set(to_copy).union(to_symlink):
2471 try:
2472 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002473 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002474
Kevin Degi384b3c52014-10-16 16:02:58 -06002475 if os.path.lexists(dst):
2476 continue
David James8d201162013-10-11 17:03:19 -07002477
2478 # If the source dir doesn't exist, create an empty dir.
2479 if name in symlink_dirs and not os.path.lexists(src):
2480 os.makedirs(src)
2481
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002482 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002483 platform_utils.symlink(
2484 os.path.relpath(src, os.path.dirname(dst)), dst)
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002485 elif copy_all and not os.path.islink(dst):
2486 if os.path.isdir(src):
2487 shutil.copytree(src, dst)
2488 elif os.path.isfile(src):
2489 shutil.copy(src, dst)
2490
Conley Owens80b87fe2014-05-09 17:13:44 -07002491 # If the source file doesn't exist, ensure the destination
2492 # file doesn't either.
2493 if name in symlink_files and not os.path.lexists(src):
2494 try:
2495 os.remove(dst)
2496 except OSError:
2497 pass
2498
David James8d201162013-10-11 17:03:19 -07002499 except OSError as e:
2500 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002501 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002502 else:
2503 raise
2504
Martin Kellye4e94d22017-03-21 16:05:12 -07002505 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002507 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002508 try:
2509 if init_dotgit:
2510 os.makedirs(dotgit)
2511 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2512 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002513
Kevin Degiabaa7f32014-11-12 11:27:45 -07002514 try:
2515 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2516 except GitError as e:
2517 if force_sync:
2518 try:
2519 shutil.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002520 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002521 except:
2522 raise e
2523 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002524
Kevin Degib1a07b82015-07-27 13:33:43 -06002525 if init_dotgit:
2526 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002527
Kevin Degib1a07b82015-07-27 13:33:43 -06002528 cmd = ['read-tree', '--reset', '-u']
2529 cmd.append('-v')
2530 cmd.append(HEAD)
2531 if GitCommand(self, cmd).Wait() != 0:
2532 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002533
Martin Kellye4e94d22017-03-21 16:05:12 -07002534 if submodules:
2535 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002536 self._CopyAndLinkFiles()
2537 except Exception:
2538 if init_dotgit:
2539 shutil.rmtree(dotgit)
2540 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002541
2542 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002543 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002544
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002545 def _revlist(self, *args, **kw):
2546 a = []
2547 a.extend(args)
2548 a.append('--')
2549 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002550
2551 @property
2552 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002553 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002554
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002555 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002556 """Get logs between two revisions of this project."""
2557 comp = '..'
2558 if rev1:
2559 revs = [rev1]
2560 if rev2:
2561 revs.extend([comp, rev2])
2562 cmd = ['log', ''.join(revs)]
2563 out = DiffColoring(self.config)
2564 if out.is_on and color:
2565 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002566 if pretty_format is not None:
2567 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002568 if oneline:
2569 cmd.append('--oneline')
2570
2571 try:
2572 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2573 if log.Wait() == 0:
2574 return log.stdout
2575 except GitError:
2576 # worktree may not exist if groups changed for example. In that case,
2577 # try in gitdir instead.
2578 if not os.path.exists(self.worktree):
2579 return self.bare_git.log(*cmd[1:])
2580 else:
2581 raise
2582 return None
2583
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002584 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2585 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002586 """Get the list of logs from this revision to given revisionId"""
2587 logs = {}
2588 selfId = self.GetRevisionId(self._allrefs)
2589 toId = toProject.GetRevisionId(toProject._allrefs)
2590
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002591 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2592 pretty_format=pretty_format)
2593 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2594 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002595 return logs
2596
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002598
David James8d201162013-10-11 17:03:19 -07002599 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002600 self._project = project
2601 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002602 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002604 def LsOthers(self):
2605 p = GitCommand(self._project,
2606 ['ls-files',
2607 '-z',
2608 '--others',
2609 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002610 bare=False,
David James8d201162013-10-11 17:03:19 -07002611 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002612 capture_stdout=True,
2613 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614 if p.Wait() == 0:
2615 out = p.stdout
2616 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002617 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002618 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002619 return []
2620
2621 def DiffZ(self, name, *args):
2622 cmd = [name]
2623 cmd.append('-z')
2624 cmd.extend(args)
2625 p = GitCommand(self._project,
2626 cmd,
David James8d201162013-10-11 17:03:19 -07002627 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002628 bare=False,
2629 capture_stdout=True,
2630 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002631 try:
2632 out = p.process.stdout.read()
2633 r = {}
2634 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002635 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002637 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002638 info = next(out)
2639 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002640 except StopIteration:
2641 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642
2643 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645 def __init__(self, path, omode, nmode, oid, nid, state):
2646 self.path = path
2647 self.src_path = None
2648 self.old_mode = omode
2649 self.new_mode = nmode
2650 self.old_id = oid
2651 self.new_id = nid
2652
2653 if len(state) == 1:
2654 self.status = state
2655 self.level = None
2656 else:
2657 self.status = state[:1]
2658 self.level = state[1:]
2659 while self.level.startswith('0'):
2660 self.level = self.level[1:]
2661
2662 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002663 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002664 if info.status in ('R', 'C'):
2665 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002666 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002667 r[info.path] = info
2668 return r
2669 finally:
2670 p.Wait()
2671
2672 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002673 if self._bare:
2674 path = os.path.join(self._project.gitdir, HEAD)
2675 else:
2676 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002677 try:
2678 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002679 except IOError as e:
2680 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002681 try:
2682 line = fd.read()
2683 finally:
2684 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302685 try:
2686 line = line.decode()
2687 except AttributeError:
2688 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002689 if line.startswith('ref: '):
2690 return line[5:-1]
2691 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002692
2693 def SetHead(self, ref, message=None):
2694 cmdv = []
2695 if message is not None:
2696 cmdv.extend(['-m', message])
2697 cmdv.append(HEAD)
2698 cmdv.append(ref)
2699 self.symbolic_ref(*cmdv)
2700
2701 def DetachHead(self, new, message=None):
2702 cmdv = ['--no-deref']
2703 if message is not None:
2704 cmdv.extend(['-m', message])
2705 cmdv.append(HEAD)
2706 cmdv.append(new)
2707 self.update_ref(*cmdv)
2708
2709 def UpdateRef(self, name, new, old=None,
2710 message=None,
2711 detach=False):
2712 cmdv = []
2713 if message is not None:
2714 cmdv.extend(['-m', message])
2715 if detach:
2716 cmdv.append('--no-deref')
2717 cmdv.append(name)
2718 cmdv.append(new)
2719 if old is not None:
2720 cmdv.append(old)
2721 self.update_ref(*cmdv)
2722
2723 def DeleteRef(self, name, old=None):
2724 if not old:
2725 old = self.rev_parse(name)
2726 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002727 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002728
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002729 def rev_list(self, *args, **kw):
2730 if 'format' in kw:
2731 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2732 else:
2733 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002734 cmdv.extend(args)
2735 p = GitCommand(self._project,
2736 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002737 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002738 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002739 capture_stdout=True,
2740 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002741 r = []
2742 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002743 if line[-1] == '\n':
2744 line = line[:-1]
2745 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002746 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002747 raise GitError('%s rev-list %s: %s' %
2748 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002749 return r
2750
2751 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002752 """Allow arbitrary git commands using pythonic syntax.
2753
2754 This allows you to do things like:
2755 git_obj.rev_parse('HEAD')
2756
2757 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2758 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002759 Any other positional arguments will be passed to the git command, and the
2760 following keyword arguments are supported:
2761 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002762
2763 Args:
2764 name: The name of the git command to call. Any '_' characters will
2765 be replaced with '-'.
2766
2767 Returns:
2768 A callable object that will try to call git with the named command.
2769 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002770 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002771
Dave Borowitz091f8932012-10-23 17:01:04 -07002772 def runner(*args, **kwargs):
2773 cmdv = []
2774 config = kwargs.pop('config', None)
2775 for k in kwargs:
2776 raise TypeError('%s() got an unexpected keyword argument %r'
2777 % (name, k))
2778 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002779 if not git_require((1, 7, 2)):
2780 raise ValueError('cannot set config on command line for %s()'
2781 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302782 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002783 cmdv.append('-c')
2784 cmdv.append('%s=%s' % (k, v))
2785 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002786 cmdv.extend(args)
2787 p = GitCommand(self._project,
2788 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002789 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002790 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002791 capture_stdout=True,
2792 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002794 raise GitError('%s %s: %s' %
2795 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002796 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302797 try:
Conley Owensedd01512013-09-26 12:59:58 -07002798 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302799 except AttributeError:
2800 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002801 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2802 return r[:-1]
2803 return r
2804 return runner
2805
2806
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002807class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002808
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002809 def __str__(self):
2810 return 'prior sync failed; rebase still in progress'
2811
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002812
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002813class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002814
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002815 def __str__(self):
2816 return 'contains uncommitted changes'
2817
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002818
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002819class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002820
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002821 def __init__(self, project, text):
2822 self.project = project
2823 self.text = text
2824
2825 def Print(self, syncbuf):
2826 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2827 syncbuf.out.nl()
2828
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002829
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002830class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002831
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002832 def __init__(self, project, why):
2833 self.project = project
2834 self.why = why
2835
2836 def Print(self, syncbuf):
2837 syncbuf.out.fail('error: %s/: %s',
2838 self.project.relpath,
2839 str(self.why))
2840 syncbuf.out.nl()
2841
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002842
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002843class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002844
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002845 def __init__(self, project, action):
2846 self.project = project
2847 self.action = action
2848
2849 def Run(self, syncbuf):
2850 out = syncbuf.out
2851 out.project('project %s/', self.project.relpath)
2852 out.nl()
2853 try:
2854 self.action()
2855 out.nl()
2856 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002857 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002858 out.nl()
2859 return False
2860
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002861
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002862class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002863
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002864 def __init__(self, config):
2865 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002866 self.project = self.printer('header', attr='bold')
2867 self.info = self.printer('info')
2868 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002869
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002870
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002871class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002872
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002873 def __init__(self, config, detach_head=False):
2874 self._messages = []
2875 self._failures = []
2876 self._later_queue1 = []
2877 self._later_queue2 = []
2878
2879 self.out = _SyncColoring(config)
2880 self.out.redirect(sys.stderr)
2881
2882 self.detach_head = detach_head
2883 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002884 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002885
2886 def info(self, project, fmt, *args):
2887 self._messages.append(_InfoMessage(project, fmt % args))
2888
2889 def fail(self, project, err=None):
2890 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002891 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002892
2893 def later1(self, project, what):
2894 self._later_queue1.append(_Later(project, what))
2895
2896 def later2(self, project, what):
2897 self._later_queue2.append(_Later(project, what))
2898
2899 def Finish(self):
2900 self._PrintMessages()
2901 self._RunLater()
2902 self._PrintMessages()
2903 return self.clean
2904
David Rileye0684ad2017-04-05 00:02:59 -07002905 def Recently(self):
2906 recent_clean = self.recent_clean
2907 self.recent_clean = True
2908 return recent_clean
2909
2910 def _MarkUnclean(self):
2911 self.clean = False
2912 self.recent_clean = False
2913
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002914 def _RunLater(self):
2915 for q in ['_later_queue1', '_later_queue2']:
2916 if not self._RunQueue(q):
2917 return
2918
2919 def _RunQueue(self, queue):
2920 for m in getattr(self, queue):
2921 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07002922 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002923 return False
2924 setattr(self, queue, [])
2925 return True
2926
2927 def _PrintMessages(self):
2928 for m in self._messages:
2929 m.Print(self)
2930 for m in self._failures:
2931 m.Print(self)
2932
2933 self._messages = []
2934 self._failures = []
2935
2936
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002938
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939 """A special project housed under .repo.
2940 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002941
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002942 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002943 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002944 manifest=manifest,
2945 name=name,
2946 gitdir=gitdir,
2947 objdir=gitdir,
2948 worktree=worktree,
2949 remote=RemoteSpec('origin'),
2950 relpath='.repo/%s' % name,
2951 revisionExpr='refs/heads/master',
2952 revisionId=None,
2953 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954
2955 def PreSync(self):
2956 if self.Exists:
2957 cb = self.CurrentBranch
2958 if cb:
2959 base = self.GetBranch(cb).merge
2960 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002961 self.revisionExpr = base
2962 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002963
Anthony King7bdac712014-07-16 12:56:40 +01002964 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002965 """ Prepare MetaProject for manifest branch switch
2966 """
2967
2968 # detach and delete manifest branch, allowing a new
2969 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002970 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002971 self.Sync_LocalHalf(syncbuf)
2972 syncbuf.Finish()
2973
2974 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002975 ['update-ref', '-d', 'refs/heads/default'],
2976 capture_stdout=True,
2977 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002978
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002979 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002980 def LastFetch(self):
2981 try:
2982 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2983 return os.path.getmtime(fh)
2984 except OSError:
2985 return 0
2986
2987 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 def HasChanges(self):
2989 """Has the remote received new commits not yet checked out?
2990 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002991 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002992 return False
2993
David Pursehouse8a68ff92012-09-24 12:15:13 +09002994 all_refs = self.bare_ref.all
2995 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002996 head = self.work_git.GetHead()
2997 if head.startswith(R_HEADS):
2998 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002999 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003000 except KeyError:
3001 head = None
3002
3003 if revid == head:
3004 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003005 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003006 return True
3007 return False