blob: 269fd7e5f505c6dfe537644535d5ed0b1bcd33a7 [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
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040043if is_python3():
44 import urllib.parse
45else:
46 import imp
47 import urlparse
48 urllib = imp.new_module('urllib')
49 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090050 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053051 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090052 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053053
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070054
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055def _lwrite(path, content):
56 lock = '%s.lock' % path
57
Chirayu Desai303a82f2014-08-19 22:57:17 +053058 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 try:
60 fd.write(content)
61 finally:
62 fd.close()
63
64 try:
65 os.rename(lock, path)
66 except OSError:
67 os.remove(lock)
68 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
Jonathan Nieder93719792015-03-17 11:29:58 -070088_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070089
90
Jonathan Nieder93719792015-03-17 11:29:58 -070091def _ProjectHooks():
92 """List the hooks present in the 'hooks' directory.
93
94 These hooks are project hooks and are copied to the '.git/hooks' directory
95 of all subprojects.
96
97 This function caches the list of hooks (based on the contents of the
98 'repo/hooks' directory) on the first call.
99
100 Returns:
101 A list of absolute paths to all of the files in the hooks directory.
102 """
103 global _project_hook_list
104 if _project_hook_list is None:
105 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
106 d = os.path.join(d, 'hooks')
107 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
108 return _project_hook_list
109
110
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700111class DownloadedChange(object):
112 _commit_cache = None
113
114 def __init__(self, project, base, change_id, ps_id, commit):
115 self.project = project
116 self.base = base
117 self.change_id = change_id
118 self.ps_id = ps_id
119 self.commit = commit
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700124 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700150 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
151 '--abbrev-commit',
152 '--pretty=oneline',
153 '--reverse',
154 '--date-order',
155 not_rev(self.base),
156 R_HEADS + self.name,
157 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700176 def UploadForReview(self, people,
177 auto_topic=False,
178 draft=False,
179 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
Brian Harring435370c2012-07-28 15:37:04 -0700182 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400183 draft=draft,
184 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700186 def GetPublishedRefs(self):
187 refs = {}
188 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700189 self.branch.remote.SshReviewUrl(self.project.UserEmail),
190 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700191 for line in output.split('\n'):
192 try:
193 (sha, ref) = line.split()
194 refs[sha] = ref
195 except ValueError:
196 pass
197
198 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 def __init__(self, config):
204 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100205 self.project = self.printer('header', attr='bold')
206 self.branch = self.printer('header', attr='bold')
207 self.nobranch = self.printer('nobranch', fg='red')
208 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
Anthony King7bdac712014-07-16 12:56:40 +0100210 self.added = self.printer('added', fg='green')
211 self.changed = self.printer('changed', fg='red')
212 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
214
215class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700216
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 def __init__(self, config):
218 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100219 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221
Anthony King7bdac712014-07-16 12:56:40 +0100222class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
James W. Mills24c13082012-04-12 15:04:13 -0500224 def __init__(self, name, value, keep):
225 self.name = name
226 self.value = value
227 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229
Anthony King7bdac712014-07-16 12:56:40 +0100230class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800232 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233 self.src = src
234 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800235 self.abs_src = abssrc
236 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
238 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 src = self.abs_src
240 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 # copy file if it does not exist or is out of date
242 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
243 try:
244 # remove existing file first, since it might be read-only
245 if os.path.exists(dest):
246 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400247 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200248 dest_dir = os.path.dirname(dest)
249 if not os.path.isdir(dest_dir):
250 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 shutil.copy(src, dest)
252 # make the file read-only
253 mode = os.stat(dest)[stat.ST_MODE]
254 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
255 os.chmod(dest, mode)
256 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700257 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700259
Anthony King7bdac712014-07-16 12:56:40 +0100260class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Wink Saville4c426ef2015-06-03 08:05:17 -0700262 def __init__(self, git_worktree, src, dest, relsrc, absdest):
263 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500264 self.src = src
265 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700266 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500267 self.abs_dest = absdest
268
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500270 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700271 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 try:
273 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800274 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700275 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700277 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500278 if not os.path.isdir(dest_dir):
279 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700280 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 _error('Cannot link file %s to %s', relSrc, absDest)
283
284 def _Link(self):
285 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
286 on the src linking all of the files in the source in to the destination
287 directory.
288 """
289 # We use the absSrc to handle the situation where the current directory
290 # is not the root of the repo
291 absSrc = os.path.join(self.git_worktree, self.src)
292 if os.path.exists(absSrc):
293 # Entity exists so just a simple one to one link operation
294 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
295 else:
296 # Entity doesn't exist assume there is a wild card
297 absDestDir = self.abs_dest
298 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
299 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700300 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700301 else:
302 absSrcFiles = glob.glob(absSrc)
303 for absSrcFile in absSrcFiles:
304 # Create a releative path from source dir to destination dir
305 absSrcDir = os.path.dirname(absSrcFile)
306 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
307
308 # Get the source file name
309 srcFile = os.path.basename(absSrcFile)
310
311 # Now form the final full paths to srcFile. They will be
312 # absolute for the desintaiton and relative for the srouce.
313 absDest = os.path.join(absDestDir, srcFile)
314 relSrc = os.path.join(relSrcDir, srcFile)
315 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500316
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700317
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700318class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700319
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700320 def __init__(self,
321 name,
Anthony King7bdac712014-07-16 12:56:40 +0100322 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700323 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100324 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700325 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700326 orig_name=None,
327 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700328 self.name = name
329 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700330 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700331 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100332 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700333 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700334 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700336
Doug Anderson37282b42011-03-04 11:54:18 -0800337class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700338
Doug Anderson37282b42011-03-04 11:54:18 -0800339 """A RepoHook contains information about a script to run as a hook.
340
341 Hooks are used to run a python script before running an upload (for instance,
342 to run presubmit checks). Eventually, we may have hooks for other actions.
343
344 This shouldn't be confused with files in the 'repo/hooks' directory. Those
345 files are copied into each '.git/hooks' folder for each project. Repo-level
346 hooks are associated instead with repo actions.
347
348 Hooks are always python. When a hook is run, we will load the hook into the
349 interpreter and execute its main() function.
350 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700351
Doug Anderson37282b42011-03-04 11:54:18 -0800352 def __init__(self,
353 hook_type,
354 hooks_project,
355 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400356 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800357 abort_if_user_denies=False):
358 """RepoHook constructor.
359
360 Params:
361 hook_type: A string representing the type of hook. This is also used
362 to figure out the name of the file containing the hook. For
363 example: 'pre-upload'.
364 hooks_project: The project containing the repo hooks. If you have a
365 manifest, this is manifest.repo_hooks_project. OK if this is None,
366 which will make the hook a no-op.
367 topdir: Repo's top directory (the one containing the .repo directory).
368 Scripts will run with CWD as this directory. If you have a manifest,
369 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400370 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800371 abort_if_user_denies: If True, we'll throw a HookError() if the user
372 doesn't allow us to run the hook.
373 """
374 self._hook_type = hook_type
375 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400376 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800377 self._topdir = topdir
378 self._abort_if_user_denies = abort_if_user_denies
379
380 # Store the full path to the script for convenience.
381 if self._hooks_project:
382 self._script_fullpath = os.path.join(self._hooks_project.worktree,
383 self._hook_type + '.py')
384 else:
385 self._script_fullpath = None
386
387 def _GetHash(self):
388 """Return a hash of the contents of the hooks directory.
389
390 We'll just use git to do this. This hash has the property that if anything
391 changes in the directory we will return a different has.
392
393 SECURITY CONSIDERATION:
394 This hash only represents the contents of files in the hook directory, not
395 any other files imported or called by hooks. Changes to imported files
396 can change the script behavior without affecting the hash.
397
398 Returns:
399 A string representing the hash. This will always be ASCII so that it can
400 be printed to the user easily.
401 """
402 assert self._hooks_project, "Must have hooks to calculate their hash."
403
404 # We will use the work_git object rather than just calling GetRevisionId().
405 # That gives us a hash of the latest checked in version of the files that
406 # the user will actually be executing. Specifically, GetRevisionId()
407 # doesn't appear to change even if a user checks out a different version
408 # of the hooks repo (via git checkout) nor if a user commits their own revs.
409 #
410 # NOTE: Local (non-committed) changes will not be factored into this hash.
411 # I think this is OK, since we're really only worried about warning the user
412 # about upstream changes.
413 return self._hooks_project.work_git.rev_parse('HEAD')
414
415 def _GetMustVerb(self):
416 """Return 'must' if the hook is required; 'should' if not."""
417 if self._abort_if_user_denies:
418 return 'must'
419 else:
420 return 'should'
421
422 def _CheckForHookApproval(self):
423 """Check to see whether this hook has been approved.
424
Mike Frysinger40252c22016-08-15 21:23:44 -0400425 We'll accept approval of manifest URLs if they're using secure transports.
426 This way the user can say they trust the manifest hoster. For insecure
427 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800428
429 Note that we ask permission for each individual hook even though we use
430 the hash of all hooks when detecting changes. We'd like the user to be
431 able to approve / deny each hook individually. We only use the hash of all
432 hooks because there is no other easy way to detect changes to local imports.
433
434 Returns:
435 True if this hook is approved to run; False otherwise.
436
437 Raises:
438 HookError: Raised if the user doesn't approve and abort_if_user_denies
439 was passed to the consturctor.
440 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400441 if self._ManifestUrlHasSecureScheme():
442 return self._CheckForHookApprovalManifest()
443 else:
444 return self._CheckForHookApprovalHash()
445
446 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
447 changed_prompt):
448 """Check for approval for a particular attribute and hook.
449
450 Args:
451 subkey: The git config key under [repo.hooks.<hook_type>] to store the
452 last approved string.
453 new_val: The new value to compare against the last approved one.
454 main_prompt: Message to display to the user to ask for approval.
455 changed_prompt: Message explaining why we're re-asking for approval.
456
457 Returns:
458 True if this hook is approved to run; False otherwise.
Doug Anderson37282b42011-03-04 11:54:18 -0800459
Mike Frysinger40252c22016-08-15 21:23:44 -0400460 Raises:
461 HookError: Raised if the user doesn't approve and abort_if_user_denies
462 was passed to the consturctor.
463 """
464 hooks_config = self._hooks_project.config
465 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800466
Mike Frysinger40252c22016-08-15 21:23:44 -0400467 # Get the last value that the user approved for this hook; may be None.
468 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800469
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800471 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800473 # Approval matched. We're done.
474 return True
475 else:
476 # Give the user a reason why we're prompting, since they last told
477 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400478 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800479 else:
480 prompt = ''
481
482 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
483 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400484 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530485 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900486 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800487
488 # User is doing a one-time approval.
489 if response in ('y', 'yes'):
490 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400491 elif response == 'always':
492 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800493 return True
494
495 # For anything else, we'll assume no approval.
496 if self._abort_if_user_denies:
497 raise HookError('You must allow the %s hook or use --no-verify.' %
498 self._hook_type)
499
500 return False
501
Mike Frysinger40252c22016-08-15 21:23:44 -0400502 def _ManifestUrlHasSecureScheme(self):
503 """Check if the URI for the manifest is a secure transport."""
504 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
505 parse_results = urllib.parse.urlparse(self._manifest_url)
506 return parse_results.scheme in secure_schemes
507
508 def _CheckForHookApprovalManifest(self):
509 """Check whether the user has approved this manifest host.
510
511 Returns:
512 True if this hook is approved to run; False otherwise.
513 """
514 return self._CheckForHookApprovalHelper(
515 'approvedmanifest',
516 self._manifest_url,
517 'Run hook scripts from %s' % (self._manifest_url,),
518 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
519
520 def _CheckForHookApprovalHash(self):
521 """Check whether the user has approved the hooks repo.
522
523 Returns:
524 True if this hook is approved to run; False otherwise.
525 """
526 prompt = ('Repo %s run the script:\n'
527 ' %s\n'
528 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700529 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400530 return self._CheckForHookApprovalHelper(
531 'approvedhash',
532 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700533 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400534 'Scripts have changed since %s was allowed.' % (self._hook_type,))
535
Doug Anderson37282b42011-03-04 11:54:18 -0800536 def _ExecuteHook(self, **kwargs):
537 """Actually execute the given hook.
538
539 This will run the hook's 'main' function in our python interpreter.
540
541 Args:
542 kwargs: Keyword arguments to pass to the hook. These are often specific
543 to the hook type. For instance, pre-upload hooks will contain
544 a project_list.
545 """
546 # Keep sys.path and CWD stashed away so that we can always restore them
547 # upon function exit.
548 orig_path = os.getcwd()
549 orig_syspath = sys.path
550
551 try:
552 # Always run hooks with CWD as topdir.
553 os.chdir(self._topdir)
554
555 # Put the hook dir as the first item of sys.path so hooks can do
556 # relative imports. We want to replace the repo dir as [0] so
557 # hooks can't import repo files.
558 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
559
560 # Exec, storing global context in the context dict. We catch exceptions
561 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500562 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800563 try:
Anthony King70f68902014-05-05 21:15:34 +0100564 exec(compile(open(self._script_fullpath).read(),
565 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800566 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700567 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
568 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800569
570 # Running the script should have defined a main() function.
571 if 'main' not in context:
572 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
573
Doug Anderson37282b42011-03-04 11:54:18 -0800574 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
575 # We don't actually want hooks to define their main with this argument--
576 # it's there to remind them that their hook should always take **kwargs.
577 # For instance, a pre-upload hook should be defined like:
578 # def main(project_list, **kwargs):
579 #
580 # This allows us to later expand the API without breaking old hooks.
581 kwargs = kwargs.copy()
582 kwargs['hook_should_take_kwargs'] = True
583
584 # Call the main function in the hook. If the hook should cause the
585 # build to fail, it will raise an Exception. We'll catch that convert
586 # to a HookError w/ just the failing traceback.
587 try:
588 context['main'](**kwargs)
589 except Exception:
590 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700591 'above.' % (traceback.format_exc(),
592 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800593 finally:
594 # Restore sys.path and CWD.
595 sys.path = orig_syspath
596 os.chdir(orig_path)
597
598 def Run(self, user_allows_all_hooks, **kwargs):
599 """Run the hook.
600
601 If the hook doesn't exist (because there is no hooks project or because
602 this particular hook is not enabled), this is a no-op.
603
604 Args:
605 user_allows_all_hooks: If True, we will never prompt about running the
606 hook--we'll just assume it's OK to run it.
607 kwargs: Keyword arguments to pass to the hook. These are often specific
608 to the hook type. For instance, pre-upload hooks will contain
609 a project_list.
610
611 Raises:
612 HookError: If there was a problem finding the hook or the user declined
613 to run a required hook (from _CheckForHookApproval).
614 """
615 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700616 if ((not self._hooks_project) or (self._hook_type not in
617 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800618 return
619
620 # Bail with a nice error if we can't find the hook.
621 if not os.path.isfile(self._script_fullpath):
622 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
623
624 # Make sure the user is OK with running the hook.
625 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
626 return
627
628 # Run the hook with the same version of python we're using.
629 self._ExecuteHook(**kwargs)
630
631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600633 # These objects can be shared between several working trees.
634 shareable_files = ['description', 'info']
635 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
636 # These objects can only be used by a single working tree.
637 working_tree_files = ['config', 'packed-refs', 'shallow']
638 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700639
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640 def __init__(self,
641 manifest,
642 name,
643 remote,
644 gitdir,
David James8d201162013-10-11 17:03:19 -0700645 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646 worktree,
647 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700648 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800649 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100650 rebase=True,
651 groups=None,
652 sync_c=False,
653 sync_s=False,
654 clone_depth=None,
655 upstream=None,
656 parent=None,
657 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900658 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700659 optimized_fetch=False,
660 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800661 """Init a Project object.
662
663 Args:
664 manifest: The XmlManifest object.
665 name: The `name` attribute of manifest.xml's project element.
666 remote: RemoteSpec object specifying its remote's properties.
667 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700668 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800669 worktree: Absolute path of git working tree.
670 relpath: Relative path of git working tree to repo's top directory.
671 revisionExpr: The `revision` attribute of manifest.xml's project element.
672 revisionId: git commit id for checking out.
673 rebase: The `rebase` attribute of manifest.xml's project element.
674 groups: The `groups` attribute of manifest.xml's project element.
675 sync_c: The `sync-c` attribute of manifest.xml's project element.
676 sync_s: The `sync-s` attribute of manifest.xml's project element.
677 upstream: The `upstream` attribute of manifest.xml's project element.
678 parent: The parent Project object.
679 is_derived: False if the project was explicitly defined in the manifest;
680 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400681 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900682 optimized_fetch: If True, when a project is set to a sha1 revision, only
683 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700684 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800685 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686 self.manifest = manifest
687 self.name = name
688 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800689 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700690 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800691 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700692 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800693 else:
694 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700696 self.revisionExpr = revisionExpr
697
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700698 if revisionId is None \
699 and revisionExpr \
700 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700701 self.revisionId = revisionExpr
702 else:
703 self.revisionId = revisionId
704
Mike Pontillod3153822012-02-28 11:53:24 -0800705 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700706 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700707 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800708 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900709 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700710 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800711 self.parent = parent
712 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900713 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800714 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800715
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500718 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500719 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700720 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
721 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800723 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700724 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800725 else:
726 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700727 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700728 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700729 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400730 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700731 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
Doug Anderson37282b42011-03-04 11:54:18 -0800733 # This will be filled in if a project is later identified to be the
734 # project containing repo hooks.
735 self.enabled_repo_hooks = []
736
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800738 def Derived(self):
739 return self.is_derived
740
741 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600743 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744
745 @property
746 def CurrentBranch(self):
747 """Obtain the name of the currently checked out branch.
748 The branch name omits the 'refs/heads/' prefix.
749 None is returned if the project is on a detached HEAD.
750 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700751 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 if b.startswith(R_HEADS):
753 return b[len(R_HEADS):]
754 return None
755
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700756 def IsRebaseInProgress(self):
757 w = self.worktree
758 g = os.path.join(w, '.git')
759 return os.path.exists(os.path.join(g, 'rebase-apply')) \
760 or os.path.exists(os.path.join(g, 'rebase-merge')) \
761 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200762
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 def IsDirty(self, consider_untracked=True):
764 """Is the working directory modified in some way?
765 """
766 self.work_git.update_index('-q',
767 '--unmerged',
768 '--ignore-missing',
769 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900770 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771 return True
772 if self.work_git.DiffZ('diff-files'):
773 return True
774 if consider_untracked and self.work_git.LsOthers():
775 return True
776 return False
777
778 _userident_name = None
779 _userident_email = None
780
781 @property
782 def UserName(self):
783 """Obtain the user's personal name.
784 """
785 if self._userident_name is None:
786 self._LoadUserIdentity()
787 return self._userident_name
788
789 @property
790 def UserEmail(self):
791 """Obtain the user's email address. This is very likely
792 to be their Gerrit login.
793 """
794 if self._userident_email is None:
795 self._LoadUserIdentity()
796 return self._userident_email
797
798 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900799 u = self.bare_git.var('GIT_COMMITTER_IDENT')
800 m = re.compile("^(.*) <([^>]*)> ").match(u)
801 if m:
802 self._userident_name = m.group(1)
803 self._userident_email = m.group(2)
804 else:
805 self._userident_name = ''
806 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
808 def GetRemote(self, name):
809 """Get the configuration for a single remote.
810 """
811 return self.config.GetRemote(name)
812
813 def GetBranch(self, name):
814 """Get the configuration for a single branch.
815 """
816 return self.config.GetBranch(name)
817
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700818 def GetBranches(self):
819 """Get all existing local branches.
820 """
821 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900822 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700823 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700824
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530825 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700826 if name.startswith(R_HEADS):
827 name = name[len(R_HEADS):]
828 b = self.GetBranch(name)
829 b.current = name == current
830 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900831 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700832 heads[name] = b
833
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530834 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700835 if name.startswith(R_PUB):
836 name = name[len(R_PUB):]
837 b = heads.get(name)
838 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900839 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700840
841 return heads
842
Colin Cross5acde752012-03-28 20:15:45 -0700843 def MatchesGroups(self, manifest_groups):
844 """Returns true if the manifest groups specified at init should cause
845 this project to be synced.
846 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700847 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700848
Conley Owens971de8e2012-04-16 10:36:08 -0700849 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700850 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700851 manifest_groups: "-group1,group2"
852 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500853
854 The special manifest group "default" will match any project that
855 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700856 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500857 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700858 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700859 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500860 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700861
Conley Owens971de8e2012-04-16 10:36:08 -0700862 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700863 for group in expanded_manifest_groups:
864 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700865 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700866 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700867 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700868
Conley Owens971de8e2012-04-16 10:36:08 -0700869 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700871# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700872 def UncommitedFiles(self, get_all=True):
873 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700875 Args:
876 get_all: a boolean, if True - get information about all different
877 uncommitted files. If False - return as soon as any kind of
878 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500879 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700880 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500881 self.work_git.update_index('-q',
882 '--unmerged',
883 '--ignore-missing',
884 '--refresh')
885 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700886 details.append("rebase in progress")
887 if not get_all:
888 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500889
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700890 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
891 if changes:
892 details.extend(changes)
893 if not get_all:
894 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500895
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700896 changes = self.work_git.DiffZ('diff-files').keys()
897 if changes:
898 details.extend(changes)
899 if not get_all:
900 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500901
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700902 changes = self.work_git.LsOthers()
903 if changes:
904 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500905
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700906 return details
907
908 def HasChanges(self):
909 """Returns true if there are uncommitted changes.
910 """
911 if self.UncommitedFiles(get_all=False):
912 return True
913 else:
914 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500915
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600916 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200918
919 Args:
920 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600921 quiet: If True then only print the project name. Do not print
922 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 """
924 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700925 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200926 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700927 print(file=output_redir)
928 print('project %s/' % self.relpath, file=output_redir)
929 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 return
931
932 self.work_git.update_index('-q',
933 '--unmerged',
934 '--ignore-missing',
935 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700936 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
938 df = self.work_git.DiffZ('diff-files')
939 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100940 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700941 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
943 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700944 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200945 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700946 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600948 if quiet:
949 out.nl()
950 return 'DIRTY'
951
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 branch = self.CurrentBranch
953 if branch is None:
954 out.nobranch('(*** NO BRANCH ***)')
955 else:
956 out.branch('branch %s', branch)
957 out.nl()
958
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700959 if rb:
960 out.important('prior sync failed; rebase still in progress')
961 out.nl()
962
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 paths = list()
964 paths.extend(di.keys())
965 paths.extend(df.keys())
966 paths.extend(do)
967
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530968 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900969 try:
970 i = di[p]
971 except KeyError:
972 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900974 try:
975 f = df[p]
976 except KeyError:
977 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200978
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900979 if i:
980 i_status = i.status.upper()
981 else:
982 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900984 if f:
985 f_status = f.status.lower()
986 else:
987 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988
989 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800990 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700991 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 else:
993 line = ' %s%s\t%s' % (i_status, f_status, p)
994
995 if i and not f:
996 out.added('%s', line)
997 elif (i and f) or (not i and f):
998 out.changed('%s', line)
999 elif not i and not f:
1000 out.untracked('%s', line)
1001 else:
1002 out.write('%s', line)
1003 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001004
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001005 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006
pelyad67872d2012-03-28 14:49:58 +03001007 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 """Prints the status of the repository to stdout.
1009 """
1010 out = DiffColoring(self.config)
1011 cmd = ['diff']
1012 if out.is_on:
1013 cmd.append('--color')
1014 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001015 if absolute_paths:
1016 cmd.append('--src-prefix=a/%s/' % self.relpath)
1017 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 cmd.append('--')
1019 p = GitCommand(self,
1020 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001021 capture_stdout=True,
1022 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 has_diff = False
1024 for line in p.process.stdout:
1025 if not has_diff:
1026 out.nl()
1027 out.project('project %s/' % self.relpath)
1028 out.nl()
1029 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001030 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031 p.Wait()
1032
1033
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001034# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035
David Pursehouse8a68ff92012-09-24 12:15:13 +09001036 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 """Was the branch published (uploaded) for code review?
1038 If so, returns the SHA-1 hash of the last published
1039 state for the branch.
1040 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001041 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001042 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001043 try:
1044 return self.bare_git.rev_parse(key)
1045 except GitError:
1046 return None
1047 else:
1048 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001050 except KeyError:
1051 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052
David Pursehouse8a68ff92012-09-24 12:15:13 +09001053 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 """Prunes any stale published refs.
1055 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001056 if all_refs is None:
1057 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 heads = set()
1059 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301060 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 if name.startswith(R_HEADS):
1062 heads.add(name)
1063 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001064 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301066 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 n = name[len(R_PUB):]
1068 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001069 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001071 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 """List any branches which can be uploaded for review.
1073 """
1074 heads = {}
1075 pubed = {}
1076
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301077 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001079 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001081 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
1083 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301084 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001085 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001087 if selected_branch and branch != selected_branch:
1088 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001090 rb = self.GetUploadableBranch(branch)
1091 if rb:
1092 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093 return ready
1094
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001095 def GetUploadableBranch(self, branch_name):
1096 """Get a single uploadable branch, or None.
1097 """
1098 branch = self.GetBranch(branch_name)
1099 base = branch.LocalMerge
1100 if branch.LocalMerge:
1101 rb = ReviewableBranch(self, branch, base)
1102 if rb.commits:
1103 return rb
1104 return None
1105
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001106 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001107 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001108 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001109 draft=False,
1110 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 """Uploads the named branch for code review.
1112 """
1113 if branch is None:
1114 branch = self.CurrentBranch
1115 if branch is None:
1116 raise GitError('not currently on a branch')
1117
1118 branch = self.GetBranch(branch)
1119 if not branch.LocalMerge:
1120 raise GitError('branch %s does not track a remote' % branch.name)
1121 if not branch.remote.review:
1122 raise GitError('remote %s has no review url' % branch.remote.name)
1123
Bryan Jacobsf609f912013-05-06 13:36:24 -04001124 if dest_branch is None:
1125 dest_branch = self.dest_branch
1126 if dest_branch is None:
1127 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128 if not dest_branch.startswith(R_HEADS):
1129 dest_branch = R_HEADS + dest_branch
1130
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001131 if not branch.remote.projectname:
1132 branch.remote.projectname = self.name
1133 branch.remote.Save()
1134
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001135 url = branch.remote.ReviewUrl(self.UserEmail)
1136 if url is None:
1137 raise UploadError('review not configured')
1138 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001139
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001140 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001141 rp = ['gerrit receive-pack']
1142 for e in people[0]:
1143 rp.append('--reviewer=%s' % sq(e))
1144 for e in people[1]:
1145 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001146 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001147
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001148 cmd.append(url)
1149
1150 if dest_branch.startswith(R_HEADS):
1151 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001152
1153 upload_type = 'for'
1154 if draft:
1155 upload_type = 'drafts'
1156
1157 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1158 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001159 if auto_topic:
1160 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001161 if not url.startswith('ssh://'):
1162 rp = ['r=%s' % p for p in people[0]] + \
1163 ['cc=%s' % p for p in people[1]]
1164 if rp:
1165 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001166 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001167
Anthony King7bdac712014-07-16 12:56:40 +01001168 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001169 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
1171 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1172 self.bare_git.UpdateRef(R_PUB + branch.name,
1173 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001174 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175
1176
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001177# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178
Julien Campergue335f5ef2013-10-16 11:02:35 +02001179 def _ExtractArchive(self, tarpath, path=None):
1180 """Extract the given tar on its current location
1181
1182 Args:
1183 - tarpath: The path to the actual tar file
1184
1185 """
1186 try:
1187 with tarfile.open(tarpath, 'r') as tar:
1188 tar.extractall(path=path)
1189 return True
1190 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001191 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001192 return False
1193
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001194 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001195 quiet=False,
1196 is_new=None,
1197 current_branch_only=False,
1198 force_sync=False,
1199 clone_bundle=True,
1200 no_tags=False,
1201 archive=False,
1202 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001203 prune=False,
1204 submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 """Perform only the network IO portion of the sync process.
1206 Local working directory/branch state is not affected.
1207 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001208 if archive and not isinstance(self, MetaProject):
1209 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001210 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001211 return False
1212
1213 name = self.relpath.replace('\\', '/')
1214 name = name.replace('/', '_')
1215 tarpath = '%s.tar' % name
1216 topdir = self.manifest.topdir
1217
1218 try:
1219 self._FetchArchive(tarpath, cwd=topdir)
1220 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001221 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001222 return False
1223
1224 # From now on, we only need absolute tarpath
1225 tarpath = os.path.join(topdir, tarpath)
1226
1227 if not self._ExtractArchive(tarpath, path=topdir):
1228 return False
1229 try:
1230 os.remove(tarpath)
1231 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001232 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001233 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001234 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001235 if is_new is None:
1236 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001237 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001238 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001239 else:
1240 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001241 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001242
1243 if is_new:
1244 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1245 try:
1246 fd = open(alt, 'rb')
1247 try:
1248 alt_dir = fd.readline().rstrip()
1249 finally:
1250 fd.close()
1251 except IOError:
1252 alt_dir = None
1253 else:
1254 alt_dir = None
1255
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001256 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001257 and alt_dir is None \
1258 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001259 is_new = False
1260
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001261 if not current_branch_only:
1262 if self.sync_c:
1263 current_branch_only = True
1264 elif not self.manifest._loaded:
1265 # Manifest cannot check defaults until it syncs.
1266 current_branch_only = False
1267 elif self.manifest.default.sync_c:
1268 current_branch_only = True
1269
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001270 if self.clone_depth:
1271 depth = self.clone_depth
1272 else:
1273 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1274
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001275 need_to_fetch = not (optimized_fetch and
1276 (ID_RE.match(self.revisionExpr) and
1277 self._CheckForSha1()))
1278 if (need_to_fetch and
1279 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1280 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001281 no_tags=no_tags, prune=prune, depth=depth,
1282 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001283 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001284
1285 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001286 self._InitMRef()
1287 else:
1288 self._InitMirrorHead()
1289 try:
1290 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1291 except OSError:
1292 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001294
1295 def PostRepoUpgrade(self):
1296 self._InitHooks()
1297
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001298 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001299 if self.manifest.isGitcClient:
1300 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001301 for copyfile in self.copyfiles:
1302 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001303 for linkfile in self.linkfiles:
1304 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
Julien Camperguedd654222014-01-09 16:21:37 +01001306 def GetCommitRevisionId(self):
1307 """Get revisionId of a commit.
1308
1309 Use this method instead of GetRevisionId to get the id of the commit rather
1310 than the id of the current git object (for example, a tag)
1311
1312 """
1313 if not self.revisionExpr.startswith(R_TAGS):
1314 return self.GetRevisionId(self._allrefs)
1315
1316 try:
1317 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1318 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001319 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1320 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001321
David Pursehouse8a68ff92012-09-24 12:15:13 +09001322 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001323 if self.revisionId:
1324 return self.revisionId
1325
1326 rem = self.GetRemote(self.remote.name)
1327 rev = rem.ToLocal(self.revisionExpr)
1328
David Pursehouse8a68ff92012-09-24 12:15:13 +09001329 if all_refs is not None and rev in all_refs:
1330 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001331
1332 try:
1333 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1334 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001335 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1336 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001337
Martin Kellye4e94d22017-03-21 16:05:12 -07001338 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 """Perform only the local IO portion of the sync process.
1340 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001342 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001343 all_refs = self.bare_ref.all
1344 self.CleanPublishedCache(all_refs)
1345 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001346
David Pursehouse1d947b32012-10-25 12:23:11 +09001347 def _doff():
1348 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001349 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001350
Martin Kellye4e94d22017-03-21 16:05:12 -07001351 def _dosubmodules():
1352 self._SyncSubmodules(quiet=True)
1353
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001354 head = self.work_git.GetHead()
1355 if head.startswith(R_HEADS):
1356 branch = head[len(R_HEADS):]
1357 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001358 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001359 except KeyError:
1360 head = None
1361 else:
1362 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001364 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 # Currently on a detached HEAD. The user is assumed to
1366 # not have any local modifications worth worrying about.
1367 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001368 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001369 syncbuf.fail(self, _PriorSyncFailedError())
1370 return
1371
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001372 if head == revid:
1373 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001374 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001375 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001376 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001377 # The copy/linkfile config may have changed.
1378 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001379 return
1380 else:
1381 lost = self._revlist(not_rev(revid), HEAD)
1382 if lost:
1383 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001384
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001386 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001387 if submodules:
1388 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001389 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.fail(self, e)
1391 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001392 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001393 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001395 if head == revid:
1396 # No changes; don't do anything further.
1397 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001398 # The copy/linkfile config may have changed.
1399 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001400 return
1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001404 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001406 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001408 syncbuf.info(self,
1409 "leaving %s; does not track upstream",
1410 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001412 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001413 if submodules:
1414 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001415 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001416 syncbuf.fail(self, e)
1417 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001418 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001419 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001421 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001422 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001424 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425 if not_merged:
1426 if upstream_gain:
1427 # The user has published this branch and some of those
1428 # commits are not yet merged upstream. We do not want
1429 # to rewrite the published commits so we punt.
1430 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001431 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001432 "branch %s is published (but not merged) and is now "
1433 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001434 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001435 elif pub == head:
1436 # All published commits are merged, and thus we are a
1437 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001438 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001439 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001440 if submodules:
1441 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001442 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001443
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001444 # Examine the local commits not in the remote. Find the
1445 # last one attributed to this user, if any.
1446 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001447 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001448 last_mine = None
1449 cnt_mine = 0
1450 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301451 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001452 if committer_email == self.UserEmail:
1453 last_mine = commit_id
1454 cnt_mine += 1
1455
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001456 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001457 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458
1459 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001460 syncbuf.fail(self, _DirtyError())
1461 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001462
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001463 # If the upstream switched on us, warn the user.
1464 #
1465 if branch.merge != self.revisionExpr:
1466 if branch.merge and self.revisionExpr:
1467 syncbuf.info(self,
1468 'manifest switched %s...%s',
1469 branch.merge,
1470 self.revisionExpr)
1471 elif branch.merge:
1472 syncbuf.info(self,
1473 'manifest no longer tracks %s',
1474 branch.merge)
1475
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001476 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001477 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001478 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001479 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001480 syncbuf.info(self,
1481 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001482 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001484 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001485 if not ID_RE.match(self.revisionExpr):
1486 # in case of manifest sync the revisionExpr might be a SHA1
1487 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001488 if not branch.merge.startswith('refs/'):
1489 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 branch.Save()
1491
Mike Pontillod3153822012-02-28 11:53:24 -08001492 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001493 def _docopyandlink():
1494 self._CopyAndLinkFiles()
1495
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001496 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001497 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001498 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001499 if submodules:
1500 syncbuf.later2(self, _dosubmodules)
1501 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001502 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001504 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001505 if submodules:
1506 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001507 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001508 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001509 syncbuf.fail(self, e)
1510 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001511 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001512 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001513 if submodules:
1514 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001516 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517 # dest should already be an absolute path, but src is project relative
1518 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001519 abssrc = os.path.join(self.worktree, src)
1520 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001521
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001522 def AddLinkFile(self, src, dest, absdest):
1523 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001524 # make src relative path to dest
1525 absdestdir = os.path.dirname(absdest)
1526 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001527 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001528
James W. Mills24c13082012-04-12 15:04:13 -05001529 def AddAnnotation(self, name, value, keep):
1530 self.annotations.append(_Annotation(name, value, keep))
1531
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001532 def DownloadPatchSet(self, change_id, patch_id):
1533 """Download a single patch set of a single change to FETCH_HEAD.
1534 """
1535 remote = self.GetRemote(self.remote.name)
1536
1537 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001538 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001539 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001540 if GitCommand(self, cmd, bare=True).Wait() != 0:
1541 return None
1542 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001543 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001544 change_id,
1545 patch_id,
1546 self.bare_git.rev_parse('FETCH_HEAD'))
1547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001549# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001550
Simran Basib9a1b732015-08-20 12:19:28 -07001551 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552 """Create a new branch off the manifest's revision.
1553 """
Simran Basib9a1b732015-08-20 12:19:28 -07001554 if not branch_merge:
1555 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001556 head = self.work_git.GetHead()
1557 if head == (R_HEADS + name):
1558 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001559
David Pursehouse8a68ff92012-09-24 12:15:13 +09001560 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001561 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001562 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001563 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001564 capture_stdout=True,
1565 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001566
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001567 branch = self.GetBranch(name)
1568 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001569 branch.merge = branch_merge
1570 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1571 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001572 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001573
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001574 if head.startswith(R_HEADS):
1575 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001576 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001577 except KeyError:
1578 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001579 if revid and head and revid == head:
1580 ref = os.path.join(self.gitdir, R_HEADS + name)
1581 try:
1582 os.makedirs(os.path.dirname(ref))
1583 except OSError:
1584 pass
1585 _lwrite(ref, '%s\n' % revid)
1586 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1587 'ref: %s%s\n' % (R_HEADS, name))
1588 branch.Save()
1589 return True
1590
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001592 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001593 capture_stdout=True,
1594 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001595 branch.Save()
1596 return True
1597 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598
Wink Saville02d79452009-04-10 13:01:24 -07001599 def CheckoutBranch(self, name):
1600 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001601
1602 Args:
1603 name: The name of the branch to checkout.
1604
1605 Returns:
1606 True if the checkout succeeded; False if it didn't; None if the branch
1607 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001608 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001609 rev = R_HEADS + name
1610 head = self.work_git.GetHead()
1611 if head == rev:
1612 # Already on the branch
1613 #
1614 return True
Wink Saville02d79452009-04-10 13:01:24 -07001615
David Pursehouse8a68ff92012-09-24 12:15:13 +09001616 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001617 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001619 except KeyError:
1620 # Branch does not exist in this project
1621 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001622 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001623
1624 if head.startswith(R_HEADS):
1625 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001626 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001627 except KeyError:
1628 head = None
1629
1630 if head == revid:
1631 # Same revision; just update HEAD to point to the new
1632 # target branch, but otherwise take no other action.
1633 #
1634 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1635 'ref: %s%s\n' % (R_HEADS, name))
1636 return True
Wink Saville02d79452009-04-10 13:01:24 -07001637
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001638 return GitCommand(self,
1639 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001640 capture_stdout=True,
1641 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001642
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001643 def AbandonBranch(self, name):
1644 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001645
1646 Args:
1647 name: The name of the branch to abandon.
1648
1649 Returns:
1650 True if the abandon succeeded; False if it didn't; None if the branch
1651 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001652 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001653 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001654 all_refs = self.bare_ref.all
1655 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001656 # Doesn't exist
1657 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001658
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001659 head = self.work_git.GetHead()
1660 if head == rev:
1661 # We can't destroy the branch while we are sitting
1662 # on it. Switch to a detached HEAD.
1663 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001665
David Pursehouse8a68ff92012-09-24 12:15:13 +09001666 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001667 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001668 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1669 '%s\n' % revid)
1670 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001671 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001672
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001673 return GitCommand(self,
1674 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001675 capture_stdout=True,
1676 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 def PruneHeads(self):
1679 """Prune any topic branches already merged into upstream.
1680 """
1681 cb = self.CurrentBranch
1682 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001683 left = self._allrefs
1684 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 if name.startswith(R_HEADS):
1686 name = name[len(R_HEADS):]
1687 if cb is None or name != cb:
1688 kill.append(name)
1689
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001690 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 if cb is not None \
1692 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001693 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 self.work_git.DetachHead(HEAD)
1695 kill.append(cb)
1696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001698 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700 try:
1701 self.bare_git.DetachHead(rev)
1702
1703 b = ['branch', '-d']
1704 b.extend(kill)
1705 b = GitCommand(self, b, bare=True,
1706 capture_stdout=True,
1707 capture_stderr=True)
1708 b.Wait()
1709 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001710 if ID_RE.match(old):
1711 self.bare_git.DetachHead(old)
1712 else:
1713 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001714 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001716 for branch in kill:
1717 if (R_HEADS + branch) not in left:
1718 self.CleanPublishedCache()
1719 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720
1721 if cb and cb not in kill:
1722 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001723 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724
1725 kept = []
1726 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001727 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 branch = self.GetBranch(branch)
1729 base = branch.LocalMerge
1730 if not base:
1731 base = rev
1732 kept.append(ReviewableBranch(self, branch, base))
1733 return kept
1734
1735
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001736# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001737
1738 def GetRegisteredSubprojects(self):
1739 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001740
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001741 def rec(subprojects):
1742 if not subprojects:
1743 return
1744 result.extend(subprojects)
1745 for p in subprojects:
1746 rec(p.subprojects)
1747 rec(self.subprojects)
1748 return result
1749
1750 def _GetSubmodules(self):
1751 # Unfortunately we cannot call `git submodule status --recursive` here
1752 # because the working tree might not exist yet, and it cannot be used
1753 # without a working tree in its current implementation.
1754
1755 def get_submodules(gitdir, rev):
1756 # Parse .gitmodules for submodule sub_paths and sub_urls
1757 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1758 if not sub_paths:
1759 return []
1760 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1761 # revision of submodule repository
1762 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1763 submodules = []
1764 for sub_path, sub_url in zip(sub_paths, sub_urls):
1765 try:
1766 sub_rev = sub_revs[sub_path]
1767 except KeyError:
1768 # Ignore non-exist submodules
1769 continue
1770 submodules.append((sub_rev, sub_path, sub_url))
1771 return submodules
1772
1773 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1774 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001775
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001776 def parse_gitmodules(gitdir, rev):
1777 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1778 try:
Anthony King7bdac712014-07-16 12:56:40 +01001779 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1780 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001781 except GitError:
1782 return [], []
1783 if p.Wait() != 0:
1784 return [], []
1785
1786 gitmodules_lines = []
1787 fd, temp_gitmodules_path = tempfile.mkstemp()
1788 try:
1789 os.write(fd, p.stdout)
1790 os.close(fd)
1791 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001792 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1793 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001794 if p.Wait() != 0:
1795 return [], []
1796 gitmodules_lines = p.stdout.split('\n')
1797 except GitError:
1798 return [], []
1799 finally:
1800 os.remove(temp_gitmodules_path)
1801
1802 names = set()
1803 paths = {}
1804 urls = {}
1805 for line in gitmodules_lines:
1806 if not line:
1807 continue
1808 m = re_path.match(line)
1809 if m:
1810 names.add(m.group(1))
1811 paths[m.group(1)] = m.group(2)
1812 continue
1813 m = re_url.match(line)
1814 if m:
1815 names.add(m.group(1))
1816 urls[m.group(1)] = m.group(2)
1817 continue
1818 names = sorted(names)
1819 return ([paths.get(name, '') for name in names],
1820 [urls.get(name, '') for name in names])
1821
1822 def git_ls_tree(gitdir, rev, paths):
1823 cmd = ['ls-tree', rev, '--']
1824 cmd.extend(paths)
1825 try:
Anthony King7bdac712014-07-16 12:56:40 +01001826 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1827 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001828 except GitError:
1829 return []
1830 if p.Wait() != 0:
1831 return []
1832 objects = {}
1833 for line in p.stdout.split('\n'):
1834 if not line.strip():
1835 continue
1836 object_rev, object_path = line.split()[2:4]
1837 objects[object_path] = object_rev
1838 return objects
1839
1840 try:
1841 rev = self.GetRevisionId()
1842 except GitError:
1843 return []
1844 return get_submodules(self.gitdir, rev)
1845
1846 def GetDerivedSubprojects(self):
1847 result = []
1848 if not self.Exists:
1849 # If git repo does not exist yet, querying its submodules will
1850 # mess up its states; so return here.
1851 return result
1852 for rev, path, url in self._GetSubmodules():
1853 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001854 relpath, worktree, gitdir, objdir = \
1855 self.manifest.GetSubprojectPaths(self, name, path)
1856 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001857 if project:
1858 result.extend(project.GetDerivedSubprojects())
1859 continue
David James8d201162013-10-11 17:03:19 -07001860
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001861 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001862 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001863 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001864 review=self.remote.review,
1865 revision=self.remote.revision)
1866 subproject = Project(manifest=self.manifest,
1867 name=name,
1868 remote=remote,
1869 gitdir=gitdir,
1870 objdir=objdir,
1871 worktree=worktree,
1872 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001873 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001874 revisionId=rev,
1875 rebase=self.rebase,
1876 groups=self.groups,
1877 sync_c=self.sync_c,
1878 sync_s=self.sync_s,
1879 parent=self,
1880 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001881 result.append(subproject)
1882 result.extend(subproject.GetDerivedSubprojects())
1883 return result
1884
1885
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001886# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001887 def _CheckForSha1(self):
1888 try:
1889 # if revision (sha or tag) is not present then following function
1890 # throws an error.
1891 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1892 return True
1893 except GitError:
1894 # There is no such persistent revision. We have to fetch it.
1895 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001896
Julien Campergue335f5ef2013-10-16 11:02:35 +02001897 def _FetchArchive(self, tarpath, cwd=None):
1898 cmd = ['archive', '-v', '-o', tarpath]
1899 cmd.append('--remote=%s' % self.remote.url)
1900 cmd.append('--prefix=%s/' % self.relpath)
1901 cmd.append(self.revisionExpr)
1902
1903 command = GitCommand(self, cmd, cwd=cwd,
1904 capture_stdout=True,
1905 capture_stderr=True)
1906
1907 if command.Wait() != 0:
1908 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1909
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001910 def _RemoteFetch(self, name=None,
1911 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001912 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001913 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001914 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001915 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001916 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001917 depth=None,
1918 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001919
1920 is_sha1 = False
1921 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001922 # The depth should not be used when fetching to a mirror because
1923 # it will result in a shallow repository that cannot be cloned or
1924 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001925 # The repo project should also never be synced with partial depth.
1926 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1927 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001928
Shawn Pearce69e04d82014-01-29 12:48:54 -08001929 if depth:
1930 current_branch_only = True
1931
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001932 if ID_RE.match(self.revisionExpr) is not None:
1933 is_sha1 = True
1934
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001935 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001936 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001937 # this is a tag and its sha1 value should never change
1938 tag_name = self.revisionExpr[len(R_TAGS):]
1939
1940 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001941 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001942 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001943 if is_sha1 and not depth:
1944 # When syncing a specific commit and --depth is not set:
1945 # * if upstream is explicitly specified and is not a sha1, fetch only
1946 # upstream as users expect only upstream to be fetch.
1947 # Note: The commit might not be in upstream in which case the sync
1948 # will fail.
1949 # * otherwise, fetch all branches to make sure we end up with the
1950 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001951 if self.upstream:
1952 current_branch_only = not ID_RE.match(self.upstream)
1953 else:
1954 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001955
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001956 if not name:
1957 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001958
1959 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001960 remote = self.GetRemote(name)
1961 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001962 ssh_proxy = True
1963
Shawn O. Pearce88443382010-10-08 10:02:09 +02001964 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001965 if alt_dir and 'objects' == os.path.basename(alt_dir):
1966 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001967 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1968 remote = self.GetRemote(name)
1969
David Pursehouse8a68ff92012-09-24 12:15:13 +09001970 all_refs = self.bare_ref.all
1971 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001972 tmp = set()
1973
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301974 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001975 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001976 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001977 all_refs[r] = ref_id
1978 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001979 continue
1980
David Pursehouse8a68ff92012-09-24 12:15:13 +09001981 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001982 continue
1983
David Pursehouse8a68ff92012-09-24 12:15:13 +09001984 r = 'refs/_alt/%s' % ref_id
1985 all_refs[r] = ref_id
1986 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001987 tmp.add(r)
1988
heping3d7bbc92017-04-12 19:51:47 +08001989 tmp_packed_lines = []
1990 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02001991
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301992 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001993 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08001994 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001995 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08001996 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001997
heping3d7bbc92017-04-12 19:51:47 +08001998 tmp_packed = ''.join(tmp_packed_lines)
1999 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002000 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002001 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002002 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002003
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002004 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002005
Conley Owensf97e8382015-01-21 11:12:46 -08002006 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002007 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002008 else:
2009 # If this repo has shallow objects, then we don't know which refs have
2010 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2011 # do this with projects that don't have shallow objects, since it is less
2012 # efficient.
2013 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2014 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002015
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002016 if quiet:
2017 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002018 if not self.worktree:
2019 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002020 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002021
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002022 # If using depth then we should not get all the tags since they may
2023 # be outside of the depth.
2024 if no_tags or depth:
2025 cmd.append('--no-tags')
2026 else:
2027 cmd.append('--tags')
2028
David Pursehouse74cfd272015-10-14 10:50:15 +09002029 if prune:
2030 cmd.append('--prune')
2031
Martin Kellye4e94d22017-03-21 16:05:12 -07002032 if submodules:
2033 cmd.append('--recurse-submodules=on-demand')
2034
Conley Owens80b87fe2014-05-09 17:13:44 -07002035 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002036 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002037 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002038 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002039 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002040 spec.append('tag')
2041 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002042
David Pursehouse403b64e2015-04-27 10:41:33 +09002043 if not self.manifest.IsMirror:
2044 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002045 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002046 # Shallow checkout of a specific commit, fetch from that commit and not
2047 # the heads only as the commit might be deeper in the history.
2048 spec.append(branch)
2049 else:
2050 if is_sha1:
2051 branch = self.upstream
2052 if branch is not None and branch.strip():
2053 if not branch.startswith('refs/'):
2054 branch = R_HEADS + branch
2055 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002056 cmd.extend(spec)
2057
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002058 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002059 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002060 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002061 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002062 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002063 ok = True
2064 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002065 # If needed, run the 'git remote prune' the first time through the loop
2066 elif (not _i and
2067 "error:" in gitcmd.stderr and
2068 "git remote prune" in gitcmd.stderr):
2069 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002070 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002071 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002072 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002073 break
2074 continue
Brian Harring14a66742012-09-28 20:21:57 -07002075 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002076 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2077 # in sha1 mode, we just tried sync'ing from the upstream field; it
2078 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002079 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002080 elif ret < 0:
2081 # Git died with a signal, exit immediately
2082 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002083 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002084
2085 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002086 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002087 if old_packed != '':
2088 _lwrite(packed_refs, old_packed)
2089 else:
2090 os.remove(packed_refs)
2091 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002092
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002093 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002094 # We just synced the upstream given branch; verify we
2095 # got what we wanted, else trigger a second run of all
2096 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002097 if not self._CheckForSha1():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002098 if current_branch_only and depth:
2099 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002100 return self._RemoteFetch(name=name,
2101 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002102 initial=False, quiet=quiet, alt_dir=alt_dir,
2103 depth=None)
2104 else:
2105 # Avoid infinite recursion: sync all branches with depth set to None
2106 return self._RemoteFetch(name=name, current_branch_only=False,
2107 initial=False, quiet=quiet, alt_dir=alt_dir,
2108 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002109
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002110 return ok
2111
2112 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002113 if initial and \
2114 (self.manifest.manifestProject.config.GetString('repo.depth') or
2115 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002116 return False
2117
2118 remote = self.GetRemote(self.remote.name)
2119 bundle_url = remote.url + '/clone.bundle'
2120 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002121 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2122 'persistent-http',
2123 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002124 return False
2125
2126 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2127 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002129 exist_dst = os.path.exists(bundle_dst)
2130 exist_tmp = os.path.exists(bundle_tmp)
2131
2132 if not initial and not exist_dst and not exist_tmp:
2133 return False
2134
2135 if not exist_dst:
2136 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2137 if not exist_dst:
2138 return False
2139
2140 cmd = ['fetch']
2141 if quiet:
2142 cmd.append('--quiet')
2143 if not self.worktree:
2144 cmd.append('--update-head-ok')
2145 cmd.append(bundle_dst)
2146 for f in remote.fetch:
2147 cmd.append(str(f))
2148 cmd.append('refs/tags/*:refs/tags/*')
2149
2150 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002151 if os.path.exists(bundle_dst):
2152 os.remove(bundle_dst)
2153 if os.path.exists(bundle_tmp):
2154 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002155 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002156
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002157 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002158 if os.path.exists(dstPath):
2159 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002160
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002161 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002162 if quiet:
2163 cmd += ['--silent']
2164 if os.path.exists(tmpPath):
2165 size = os.stat(tmpPath).st_size
2166 if size >= 1024:
2167 cmd += ['--continue-at', '%d' % (size,)]
2168 else:
2169 os.remove(tmpPath)
2170 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2171 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002172 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002173 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002174 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002175 if srcUrl.startswith('persistent-'):
2176 srcUrl = srcUrl[len('persistent-'):]
2177 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002178
Dave Borowitz137d0132015-01-02 11:12:54 -08002179 if IsTrace():
2180 Trace('%s', ' '.join(cmd))
2181 try:
2182 proc = subprocess.Popen(cmd)
2183 except OSError:
2184 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002185
Dave Borowitz137d0132015-01-02 11:12:54 -08002186 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002187
Dave Borowitz137d0132015-01-02 11:12:54 -08002188 if curlret == 22:
2189 # From curl man page:
2190 # 22: HTTP page not retrieved. The requested url was not found or
2191 # returned another error with the HTTP error code being 400 or above.
2192 # This return code only appears if -f, --fail is used.
2193 if not quiet:
2194 print("Server does not provide clone.bundle; ignoring.",
2195 file=sys.stderr)
2196 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002197
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002198 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002199 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002200 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002201 return True
2202 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002203 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002204 return False
2205 else:
2206 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002207
Kris Giesingc8d882a2014-12-23 13:02:32 -08002208 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002209 try:
2210 with open(path) as f:
2211 if f.read(16) == '# v2 git bundle\n':
2212 return True
2213 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002214 if not quiet:
2215 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002216 return False
2217 except OSError:
2218 return False
2219
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002220 def _Checkout(self, rev, quiet=False):
2221 cmd = ['checkout']
2222 if quiet:
2223 cmd.append('-q')
2224 cmd.append(rev)
2225 cmd.append('--')
2226 if GitCommand(self, cmd).Wait() != 0:
2227 if self._allrefs:
2228 raise GitError('%s checkout %s ' % (self.name, rev))
2229
Anthony King7bdac712014-07-16 12:56:40 +01002230 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002231 cmd = ['cherry-pick']
2232 cmd.append(rev)
2233 cmd.append('--')
2234 if GitCommand(self, cmd).Wait() != 0:
2235 if self._allrefs:
2236 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2237
Anthony King7bdac712014-07-16 12:56:40 +01002238 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002239 cmd = ['revert']
2240 cmd.append('--no-edit')
2241 cmd.append(rev)
2242 cmd.append('--')
2243 if GitCommand(self, cmd).Wait() != 0:
2244 if self._allrefs:
2245 raise GitError('%s revert %s ' % (self.name, rev))
2246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247 def _ResetHard(self, rev, quiet=True):
2248 cmd = ['reset', '--hard']
2249 if quiet:
2250 cmd.append('-q')
2251 cmd.append(rev)
2252 if GitCommand(self, cmd).Wait() != 0:
2253 raise GitError('%s reset --hard %s ' % (self.name, rev))
2254
Martin Kellye4e94d22017-03-21 16:05:12 -07002255 def _SyncSubmodules(self, quiet=True):
2256 cmd = ['submodule', 'update', '--init', '--recursive']
2257 if quiet:
2258 cmd.append('-q')
2259 if GitCommand(self, cmd).Wait() != 0:
2260 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2261
Anthony King7bdac712014-07-16 12:56:40 +01002262 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002263 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002264 if onto is not None:
2265 cmd.extend(['--onto', onto])
2266 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002267 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002268 raise GitError('%s rebase %s ' % (self.name, upstream))
2269
Pierre Tardy3d125942012-05-04 12:18:12 +02002270 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002271 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002272 if ffonly:
2273 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002274 if GitCommand(self, cmd).Wait() != 0:
2275 raise GitError('%s merge %s ' % (self.name, head))
2276
Kevin Degiabaa7f32014-11-12 11:27:45 -07002277 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002278 init_git_dir = not os.path.exists(self.gitdir)
2279 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002280 try:
2281 # Initialize the bare repository, which contains all of the objects.
2282 if init_obj_dir:
2283 os.makedirs(self.objdir)
2284 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002285
Kevin Degib1a07b82015-07-27 13:33:43 -06002286 # If we have a separate directory to hold refs, initialize it as well.
2287 if self.objdir != self.gitdir:
2288 if init_git_dir:
2289 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002290
Kevin Degib1a07b82015-07-27 13:33:43 -06002291 if init_obj_dir or init_git_dir:
2292 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2293 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002294 try:
2295 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2296 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002297 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002298 print("Retrying clone after deleting %s" %
2299 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002300 try:
2301 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002302 if self.worktree and os.path.exists(os.path.realpath
2303 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002304 shutil.rmtree(os.path.realpath(self.worktree))
2305 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2306 except:
2307 raise e
2308 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002309
Kevin Degib1a07b82015-07-27 13:33:43 -06002310 if init_git_dir:
2311 mp = self.manifest.manifestProject
2312 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002313
Kevin Degib1a07b82015-07-27 13:33:43 -06002314 if ref_dir or mirror_git:
2315 if not mirror_git:
2316 mirror_git = os.path.join(ref_dir, self.name + '.git')
2317 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2318 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002319
Kevin Degib1a07b82015-07-27 13:33:43 -06002320 if os.path.exists(mirror_git):
2321 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002322
Kevin Degib1a07b82015-07-27 13:33:43 -06002323 elif os.path.exists(repo_git):
2324 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002325
Kevin Degib1a07b82015-07-27 13:33:43 -06002326 else:
2327 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002328
Kevin Degib1a07b82015-07-27 13:33:43 -06002329 if ref_dir:
2330 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2331 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002332
Kevin Degib1a07b82015-07-27 13:33:43 -06002333 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002334
Kevin Degib1a07b82015-07-27 13:33:43 -06002335 m = self.manifest.manifestProject.config
2336 for key in ['user.name', 'user.email']:
2337 if m.Has(key, include_defaults=False):
2338 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002339 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002340 if self.manifest.IsMirror:
2341 self.config.SetString('core.bare', 'true')
2342 else:
2343 self.config.SetString('core.bare', None)
2344 except Exception:
2345 if init_obj_dir and os.path.exists(self.objdir):
2346 shutil.rmtree(self.objdir)
2347 if init_git_dir and os.path.exists(self.gitdir):
2348 shutil.rmtree(self.gitdir)
2349 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002350
Jimmie Westera0444582012-10-24 13:44:42 +02002351 def _UpdateHooks(self):
2352 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002353 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002355 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002356 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002357 if not os.path.exists(hooks):
2358 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002359 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002360 name = os.path.basename(stock_hook)
2361
Victor Boivie65e0f352011-04-18 11:23:29 +02002362 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002363 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002364 # Don't install a Gerrit Code Review hook if this
2365 # project does not appear to use it for reviews.
2366 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002367 # Since the manifest project is one of those, but also
2368 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002369 continue
2370
2371 dst = os.path.join(hooks, name)
2372 if os.path.islink(dst):
2373 continue
2374 if os.path.exists(dst):
2375 if filecmp.cmp(stock_hook, dst, shallow=False):
2376 os.remove(dst)
2377 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002378 _warn("%s: Not replacing locally modified %s hook",
2379 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002380 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002381 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002382 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002383 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002384 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002385 raise GitError('filesystem must support symlinks')
2386 else:
2387 raise
2388
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002389 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002390 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002392 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002393 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002394 remote.review = self.remote.review
2395 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002396
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002397 if self.worktree:
2398 remote.ResetFetch(mirror=False)
2399 else:
2400 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002401 remote.Save()
2402
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 def _InitMRef(self):
2404 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002405 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002406
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002407 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002408 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002409
2410 def _InitAnyMRef(self, ref):
2411 cur = self.bare_ref.symref(ref)
2412
2413 if self.revisionId:
2414 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2415 msg = 'manifest set to %s' % self.revisionId
2416 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002417 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002418 else:
2419 remote = self.GetRemote(self.remote.name)
2420 dst = remote.ToLocal(self.revisionExpr)
2421 if cur != dst:
2422 msg = 'manifest set to %s' % self.revisionExpr
2423 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002424
Kevin Degi384b3c52014-10-16 16:02:58 -06002425 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002426 symlink_files = self.shareable_files[:]
2427 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002428 if share_refs:
2429 symlink_files += self.working_tree_files
2430 symlink_dirs += self.working_tree_dirs
2431 to_symlink = symlink_files + symlink_dirs
2432 for name in set(to_symlink):
2433 dst = os.path.realpath(os.path.join(destdir, name))
2434 if os.path.lexists(dst):
2435 src = os.path.realpath(os.path.join(srcdir, name))
2436 # Fail if the links are pointing to the wrong place
2437 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002438 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002439 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002440 'work tree. If you\'re comfortable with the '
2441 'possibility of losing the work tree\'s git metadata,'
2442 ' use `repo sync --force-sync {0}` to '
2443 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002444
David James8d201162013-10-11 17:03:19 -07002445 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2446 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2447
2448 Args:
2449 gitdir: The bare git repository. Must already be initialized.
2450 dotgit: The repository you would like to initialize.
2451 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2452 Only one work tree can store refs under a given |gitdir|.
2453 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2454 This saves you the effort of initializing |dotgit| yourself.
2455 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002456 symlink_files = self.shareable_files[:]
2457 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002458 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002459 symlink_files += self.working_tree_files
2460 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002461 to_symlink = symlink_files + symlink_dirs
2462
2463 to_copy = []
2464 if copy_all:
2465 to_copy = os.listdir(gitdir)
2466
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002467 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002468 for name in set(to_copy).union(to_symlink):
2469 try:
2470 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002471 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002472
Kevin Degi384b3c52014-10-16 16:02:58 -06002473 if os.path.lexists(dst):
2474 continue
David James8d201162013-10-11 17:03:19 -07002475
2476 # If the source dir doesn't exist, create an empty dir.
2477 if name in symlink_dirs and not os.path.lexists(src):
2478 os.makedirs(src)
2479
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002480 if name in to_symlink:
2481 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2482 elif copy_all and not os.path.islink(dst):
2483 if os.path.isdir(src):
2484 shutil.copytree(src, dst)
2485 elif os.path.isfile(src):
2486 shutil.copy(src, dst)
2487
Conley Owens80b87fe2014-05-09 17:13:44 -07002488 # If the source file doesn't exist, ensure the destination
2489 # file doesn't either.
2490 if name in symlink_files and not os.path.lexists(src):
2491 try:
2492 os.remove(dst)
2493 except OSError:
2494 pass
2495
David James8d201162013-10-11 17:03:19 -07002496 except OSError as e:
2497 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002498 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002499 else:
2500 raise
2501
Martin Kellye4e94d22017-03-21 16:05:12 -07002502 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002503 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002504 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002505 try:
2506 if init_dotgit:
2507 os.makedirs(dotgit)
2508 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2509 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002510
Kevin Degiabaa7f32014-11-12 11:27:45 -07002511 try:
2512 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2513 except GitError as e:
2514 if force_sync:
2515 try:
2516 shutil.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002517 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002518 except:
2519 raise e
2520 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002521
Kevin Degib1a07b82015-07-27 13:33:43 -06002522 if init_dotgit:
2523 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002524
Kevin Degib1a07b82015-07-27 13:33:43 -06002525 cmd = ['read-tree', '--reset', '-u']
2526 cmd.append('-v')
2527 cmd.append(HEAD)
2528 if GitCommand(self, cmd).Wait() != 0:
2529 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002530
Martin Kellye4e94d22017-03-21 16:05:12 -07002531 if submodules:
2532 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002533 self._CopyAndLinkFiles()
2534 except Exception:
2535 if init_dotgit:
2536 shutil.rmtree(dotgit)
2537 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002538
2539 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002540 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002541
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002542 def _revlist(self, *args, **kw):
2543 a = []
2544 a.extend(args)
2545 a.append('--')
2546 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002547
2548 @property
2549 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002550 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002551
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002552 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002553 """Get logs between two revisions of this project."""
2554 comp = '..'
2555 if rev1:
2556 revs = [rev1]
2557 if rev2:
2558 revs.extend([comp, rev2])
2559 cmd = ['log', ''.join(revs)]
2560 out = DiffColoring(self.config)
2561 if out.is_on and color:
2562 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002563 if pretty_format is not None:
2564 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002565 if oneline:
2566 cmd.append('--oneline')
2567
2568 try:
2569 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2570 if log.Wait() == 0:
2571 return log.stdout
2572 except GitError:
2573 # worktree may not exist if groups changed for example. In that case,
2574 # try in gitdir instead.
2575 if not os.path.exists(self.worktree):
2576 return self.bare_git.log(*cmd[1:])
2577 else:
2578 raise
2579 return None
2580
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002581 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2582 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002583 """Get the list of logs from this revision to given revisionId"""
2584 logs = {}
2585 selfId = self.GetRevisionId(self._allrefs)
2586 toId = toProject.GetRevisionId(toProject._allrefs)
2587
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002588 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2589 pretty_format=pretty_format)
2590 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2591 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002592 return logs
2593
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002594 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002595
David James8d201162013-10-11 17:03:19 -07002596 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597 self._project = project
2598 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002599 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002601 def LsOthers(self):
2602 p = GitCommand(self._project,
2603 ['ls-files',
2604 '-z',
2605 '--others',
2606 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002607 bare=False,
David James8d201162013-10-11 17:03:19 -07002608 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002609 capture_stdout=True,
2610 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 if p.Wait() == 0:
2612 out = p.stdout
2613 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002614 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002615 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 return []
2617
2618 def DiffZ(self, name, *args):
2619 cmd = [name]
2620 cmd.append('-z')
2621 cmd.extend(args)
2622 p = GitCommand(self._project,
2623 cmd,
David James8d201162013-10-11 17:03:19 -07002624 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002625 bare=False,
2626 capture_stdout=True,
2627 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002628 try:
2629 out = p.process.stdout.read()
2630 r = {}
2631 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002632 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002633 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002634 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002635 info = next(out)
2636 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002637 except StopIteration:
2638 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002639
2640 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 def __init__(self, path, omode, nmode, oid, nid, state):
2643 self.path = path
2644 self.src_path = None
2645 self.old_mode = omode
2646 self.new_mode = nmode
2647 self.old_id = oid
2648 self.new_id = nid
2649
2650 if len(state) == 1:
2651 self.status = state
2652 self.level = None
2653 else:
2654 self.status = state[:1]
2655 self.level = state[1:]
2656 while self.level.startswith('0'):
2657 self.level = self.level[1:]
2658
2659 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002660 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 if info.status in ('R', 'C'):
2662 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002663 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002664 r[info.path] = info
2665 return r
2666 finally:
2667 p.Wait()
2668
2669 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002670 if self._bare:
2671 path = os.path.join(self._project.gitdir, HEAD)
2672 else:
2673 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002674 try:
2675 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002676 except IOError as e:
2677 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002678 try:
2679 line = fd.read()
2680 finally:
2681 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302682 try:
2683 line = line.decode()
2684 except AttributeError:
2685 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002686 if line.startswith('ref: '):
2687 return line[5:-1]
2688 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689
2690 def SetHead(self, ref, message=None):
2691 cmdv = []
2692 if message is not None:
2693 cmdv.extend(['-m', message])
2694 cmdv.append(HEAD)
2695 cmdv.append(ref)
2696 self.symbolic_ref(*cmdv)
2697
2698 def DetachHead(self, new, message=None):
2699 cmdv = ['--no-deref']
2700 if message is not None:
2701 cmdv.extend(['-m', message])
2702 cmdv.append(HEAD)
2703 cmdv.append(new)
2704 self.update_ref(*cmdv)
2705
2706 def UpdateRef(self, name, new, old=None,
2707 message=None,
2708 detach=False):
2709 cmdv = []
2710 if message is not None:
2711 cmdv.extend(['-m', message])
2712 if detach:
2713 cmdv.append('--no-deref')
2714 cmdv.append(name)
2715 cmdv.append(new)
2716 if old is not None:
2717 cmdv.append(old)
2718 self.update_ref(*cmdv)
2719
2720 def DeleteRef(self, name, old=None):
2721 if not old:
2722 old = self.rev_parse(name)
2723 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002724 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002726 def rev_list(self, *args, **kw):
2727 if 'format' in kw:
2728 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2729 else:
2730 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731 cmdv.extend(args)
2732 p = GitCommand(self._project,
2733 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002734 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002735 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002736 capture_stdout=True,
2737 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738 r = []
2739 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002740 if line[-1] == '\n':
2741 line = line[:-1]
2742 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002743 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002744 raise GitError('%s rev-list %s: %s' %
2745 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002746 return r
2747
2748 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002749 """Allow arbitrary git commands using pythonic syntax.
2750
2751 This allows you to do things like:
2752 git_obj.rev_parse('HEAD')
2753
2754 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2755 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002756 Any other positional arguments will be passed to the git command, and the
2757 following keyword arguments are supported:
2758 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002759
2760 Args:
2761 name: The name of the git command to call. Any '_' characters will
2762 be replaced with '-'.
2763
2764 Returns:
2765 A callable object that will try to call git with the named command.
2766 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002768
Dave Borowitz091f8932012-10-23 17:01:04 -07002769 def runner(*args, **kwargs):
2770 cmdv = []
2771 config = kwargs.pop('config', None)
2772 for k in kwargs:
2773 raise TypeError('%s() got an unexpected keyword argument %r'
2774 % (name, k))
2775 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002776 if not git_require((1, 7, 2)):
2777 raise ValueError('cannot set config on command line for %s()'
2778 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302779 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002780 cmdv.append('-c')
2781 cmdv.append('%s=%s' % (k, v))
2782 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002783 cmdv.extend(args)
2784 p = GitCommand(self._project,
2785 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002786 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002787 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002788 capture_stdout=True,
2789 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002790 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002791 raise GitError('%s %s: %s' %
2792 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302794 try:
Conley Owensedd01512013-09-26 12:59:58 -07002795 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302796 except AttributeError:
2797 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002798 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2799 return r[:-1]
2800 return r
2801 return runner
2802
2803
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002804class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002805
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002806 def __str__(self):
2807 return 'prior sync failed; rebase still in progress'
2808
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002809
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002810class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002811
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002812 def __str__(self):
2813 return 'contains uncommitted changes'
2814
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002815
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002816class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002817
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002818 def __init__(self, project, text):
2819 self.project = project
2820 self.text = text
2821
2822 def Print(self, syncbuf):
2823 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2824 syncbuf.out.nl()
2825
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002826
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002827class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002828
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002829 def __init__(self, project, why):
2830 self.project = project
2831 self.why = why
2832
2833 def Print(self, syncbuf):
2834 syncbuf.out.fail('error: %s/: %s',
2835 self.project.relpath,
2836 str(self.why))
2837 syncbuf.out.nl()
2838
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002839
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002840class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002841
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002842 def __init__(self, project, action):
2843 self.project = project
2844 self.action = action
2845
2846 def Run(self, syncbuf):
2847 out = syncbuf.out
2848 out.project('project %s/', self.project.relpath)
2849 out.nl()
2850 try:
2851 self.action()
2852 out.nl()
2853 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002854 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002855 out.nl()
2856 return False
2857
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002858
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002859class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002860
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002861 def __init__(self, config):
2862 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002863 self.project = self.printer('header', attr='bold')
2864 self.info = self.printer('info')
2865 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002866
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002867
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002868class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002869
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002870 def __init__(self, config, detach_head=False):
2871 self._messages = []
2872 self._failures = []
2873 self._later_queue1 = []
2874 self._later_queue2 = []
2875
2876 self.out = _SyncColoring(config)
2877 self.out.redirect(sys.stderr)
2878
2879 self.detach_head = detach_head
2880 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002881 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002882
2883 def info(self, project, fmt, *args):
2884 self._messages.append(_InfoMessage(project, fmt % args))
2885
2886 def fail(self, project, err=None):
2887 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002888 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002889
2890 def later1(self, project, what):
2891 self._later_queue1.append(_Later(project, what))
2892
2893 def later2(self, project, what):
2894 self._later_queue2.append(_Later(project, what))
2895
2896 def Finish(self):
2897 self._PrintMessages()
2898 self._RunLater()
2899 self._PrintMessages()
2900 return self.clean
2901
David Rileye0684ad2017-04-05 00:02:59 -07002902 def Recently(self):
2903 recent_clean = self.recent_clean
2904 self.recent_clean = True
2905 return recent_clean
2906
2907 def _MarkUnclean(self):
2908 self.clean = False
2909 self.recent_clean = False
2910
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002911 def _RunLater(self):
2912 for q in ['_later_queue1', '_later_queue2']:
2913 if not self._RunQueue(q):
2914 return
2915
2916 def _RunQueue(self, queue):
2917 for m in getattr(self, queue):
2918 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07002919 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002920 return False
2921 setattr(self, queue, [])
2922 return True
2923
2924 def _PrintMessages(self):
2925 for m in self._messages:
2926 m.Print(self)
2927 for m in self._failures:
2928 m.Print(self)
2929
2930 self._messages = []
2931 self._failures = []
2932
2933
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002934class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002935
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002936 """A special project housed under .repo.
2937 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002938
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002940 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002941 manifest=manifest,
2942 name=name,
2943 gitdir=gitdir,
2944 objdir=gitdir,
2945 worktree=worktree,
2946 remote=RemoteSpec('origin'),
2947 relpath='.repo/%s' % name,
2948 revisionExpr='refs/heads/master',
2949 revisionId=None,
2950 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002951
2952 def PreSync(self):
2953 if self.Exists:
2954 cb = self.CurrentBranch
2955 if cb:
2956 base = self.GetBranch(cb).merge
2957 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002958 self.revisionExpr = base
2959 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960
Anthony King7bdac712014-07-16 12:56:40 +01002961 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002962 """ Prepare MetaProject for manifest branch switch
2963 """
2964
2965 # detach and delete manifest branch, allowing a new
2966 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002967 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002968 self.Sync_LocalHalf(syncbuf)
2969 syncbuf.Finish()
2970
2971 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002972 ['update-ref', '-d', 'refs/heads/default'],
2973 capture_stdout=True,
2974 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002975
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002976 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002977 def LastFetch(self):
2978 try:
2979 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2980 return os.path.getmtime(fh)
2981 except OSError:
2982 return 0
2983
2984 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002985 def HasChanges(self):
2986 """Has the remote received new commits not yet checked out?
2987 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002988 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002989 return False
2990
David Pursehouse8a68ff92012-09-24 12:15:13 +09002991 all_refs = self.bare_ref.all
2992 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002993 head = self.work_git.GetHead()
2994 if head.startswith(R_HEADS):
2995 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002996 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002997 except KeyError:
2998 head = None
2999
3000 if revid == head:
3001 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003002 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003003 return True
3004 return False