blob: d6e11d256955c55a349e44da46e1a36dcec9657a [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070034 ID_RE, RefSpec
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070036from error import CacheApplyError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080037from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080038from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070039from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Shawn O. Pearced237b692009-04-17 18:49:50 -070041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
David Pursehouse59bbb582013-05-17 10:49:33 +090043from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040044if is_python3():
45 import urllib.parse
46else:
47 import imp
48 import urlparse
49 urllib = imp.new_module('urllib')
50 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090051 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053052 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090053 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070055
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056def _lwrite(path, content):
57 lock = '%s.lock' % path
58
Chirayu Desai303a82f2014-08-19 22:57:17 +053059 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 try:
61 fd.write(content)
62 finally:
63 fd.close()
64
65 try:
66 os.rename(lock, path)
67 except OSError:
68 os.remove(lock)
69 raise
70
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
Shawn O. Pearce48244782009-04-16 08:25:57 -070072def _error(fmt, *args):
73 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070074 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070075
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
David Pursehousef33929d2015-08-24 14:39:14 +090077def _warn(fmt, *args):
78 msg = fmt % args
79 print('warn: %s' % msg, file=sys.stderr)
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082def not_rev(r):
83 return '^' + r
84
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070085
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080086def sq(r):
87 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
106 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
107 d = os.path.join(d, 'hooks')
108 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
138
139 def __init__(self, project, branch, base):
140 self.project = project
141 self.branch = branch
142 self.base = base
143
144 @property
145 def name(self):
146 return self.branch.name
147
148 @property
149 def commits(self):
150 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700151 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700164 for commit in self.project.bare_git.rev_list(not_rev(self.base),
165 R_HEADS + self.name,
166 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 r[commit[0:8]] = commit
168 return r
169
170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 return self.project.bare_git.log('--pretty=format:%cd',
173 '-n', '1',
174 R_HEADS + self.name,
175 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700177 def UploadForReview(self, people,
178 auto_topic=False,
179 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200180 private=False,
Vadim Bendebury329651b2018-10-31 13:48:01 -0700181 notify=None,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200182 wip=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700183 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800184 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700185 people,
Brian Harring435370c2012-07-28 15:37:04 -0700186 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400187 draft=draft,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200188 private=private,
Vadim Bendebury329651b2018-10-31 13:48:01 -0700189 notify=notify,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200190 wip=wip,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400191 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700193 def GetPublishedRefs(self):
194 refs = {}
195 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196 self.branch.remote.SshReviewUrl(self.project.UserEmail),
197 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700198 for line in output.split('\n'):
199 try:
200 (sha, ref) = line.split()
201 refs[sha] = ref
202 except ValueError:
203 pass
204
205 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700209
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 def __init__(self, config):
211 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100212 self.project = self.printer('header', attr='bold')
213 self.branch = self.printer('header', attr='bold')
214 self.nobranch = self.printer('nobranch', fg='red')
215 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Anthony King7bdac712014-07-16 12:56:40 +0100217 self.added = self.printer('added', fg='green')
218 self.changed = self.printer('changed', fg='red')
219 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
221
222class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 def __init__(self, config):
225 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100226 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228
Anthony King7bdac712014-07-16 12:56:40 +0100229class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700230
James W. Mills24c13082012-04-12 15:04:13 -0500231 def __init__(self, name, value, keep):
232 self.name = name
233 self.value = value
234 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
Anthony King7bdac712014-07-16 12:56:40 +0100237class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 self.src = src
241 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800242 self.abs_src = abssrc
243 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800246 src = self.abs_src
247 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 # copy file if it does not exist or is out of date
249 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
250 try:
251 # remove existing file first, since it might be read-only
252 if os.path.exists(dest):
253 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400254 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200255 dest_dir = os.path.dirname(dest)
256 if not os.path.isdir(dest_dir):
257 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258 shutil.copy(src, dest)
259 # make the file read-only
260 mode = os.stat(dest)[stat.ST_MODE]
261 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
262 os.chmod(dest, mode)
263 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700264 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Anthony King7bdac712014-07-16 12:56:40 +0100267class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 def __init__(self, git_worktree, src, dest, relsrc, absdest):
270 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 self.src = src
272 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700273 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500274 self.abs_dest = absdest
275
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700278 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500279 try:
280 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800281 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500283 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700284 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500285 if not os.path.isdir(dest_dir):
286 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700287 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500288 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700289 _error('Cannot link file %s to %s', relSrc, absDest)
290
291 def _Link(self):
292 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
293 on the src linking all of the files in the source in to the destination
294 directory.
295 """
296 # We use the absSrc to handle the situation where the current directory
297 # is not the root of the repo
298 absSrc = os.path.join(self.git_worktree, self.src)
299 if os.path.exists(absSrc):
300 # Entity exists so just a simple one to one link operation
301 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
302 else:
303 # Entity doesn't exist assume there is a wild card
304 absDestDir = self.abs_dest
305 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
306 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700307 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700308 else:
309 absSrcFiles = glob.glob(absSrc)
310 for absSrcFile in absSrcFiles:
311 # Create a releative path from source dir to destination dir
312 absSrcDir = os.path.dirname(absSrcFile)
313 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
314
315 # Get the source file name
316 srcFile = os.path.basename(absSrcFile)
317
318 # Now form the final full paths to srcFile. They will be
319 # absolute for the desintaiton and relative for the srouce.
320 absDest = os.path.join(absDestDir, srcFile)
321 relSrc = os.path.join(relSrcDir, srcFile)
322 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500323
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700325class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700326
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700327 def __init__(self,
328 name,
Anthony King7bdac712014-07-16 12:56:40 +0100329 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700330 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100331 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700332 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700333 orig_name=None,
334 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700335 self.name = name
336 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700337 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700338 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100339 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700340 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700341 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700343
Doug Anderson37282b42011-03-04 11:54:18 -0800344class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700345
Doug Anderson37282b42011-03-04 11:54:18 -0800346 """A RepoHook contains information about a script to run as a hook.
347
348 Hooks are used to run a python script before running an upload (for instance,
349 to run presubmit checks). Eventually, we may have hooks for other actions.
350
351 This shouldn't be confused with files in the 'repo/hooks' directory. Those
352 files are copied into each '.git/hooks' folder for each project. Repo-level
353 hooks are associated instead with repo actions.
354
355 Hooks are always python. When a hook is run, we will load the hook into the
356 interpreter and execute its main() function.
357 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700358
Doug Anderson37282b42011-03-04 11:54:18 -0800359 def __init__(self,
360 hook_type,
361 hooks_project,
362 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400363 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800364 abort_if_user_denies=False):
365 """RepoHook constructor.
366
367 Params:
368 hook_type: A string representing the type of hook. This is also used
369 to figure out the name of the file containing the hook. For
370 example: 'pre-upload'.
371 hooks_project: The project containing the repo hooks. If you have a
372 manifest, this is manifest.repo_hooks_project. OK if this is None,
373 which will make the hook a no-op.
374 topdir: Repo's top directory (the one containing the .repo directory).
375 Scripts will run with CWD as this directory. If you have a manifest,
376 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400377 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800378 abort_if_user_denies: If True, we'll throw a HookError() if the user
379 doesn't allow us to run the hook.
380 """
381 self._hook_type = hook_type
382 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400383 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800384 self._topdir = topdir
385 self._abort_if_user_denies = abort_if_user_denies
386
387 # Store the full path to the script for convenience.
388 if self._hooks_project:
389 self._script_fullpath = os.path.join(self._hooks_project.worktree,
390 self._hook_type + '.py')
391 else:
392 self._script_fullpath = None
393
394 def _GetHash(self):
395 """Return a hash of the contents of the hooks directory.
396
397 We'll just use git to do this. This hash has the property that if anything
398 changes in the directory we will return a different has.
399
400 SECURITY CONSIDERATION:
401 This hash only represents the contents of files in the hook directory, not
402 any other files imported or called by hooks. Changes to imported files
403 can change the script behavior without affecting the hash.
404
405 Returns:
406 A string representing the hash. This will always be ASCII so that it can
407 be printed to the user easily.
408 """
409 assert self._hooks_project, "Must have hooks to calculate their hash."
410
411 # We will use the work_git object rather than just calling GetRevisionId().
412 # That gives us a hash of the latest checked in version of the files that
413 # the user will actually be executing. Specifically, GetRevisionId()
414 # doesn't appear to change even if a user checks out a different version
415 # of the hooks repo (via git checkout) nor if a user commits their own revs.
416 #
417 # NOTE: Local (non-committed) changes will not be factored into this hash.
418 # I think this is OK, since we're really only worried about warning the user
419 # about upstream changes.
420 return self._hooks_project.work_git.rev_parse('HEAD')
421
422 def _GetMustVerb(self):
423 """Return 'must' if the hook is required; 'should' if not."""
424 if self._abort_if_user_denies:
425 return 'must'
426 else:
427 return 'should'
428
429 def _CheckForHookApproval(self):
430 """Check to see whether this hook has been approved.
431
Mike Frysinger40252c22016-08-15 21:23:44 -0400432 We'll accept approval of manifest URLs if they're using secure transports.
433 This way the user can say they trust the manifest hoster. For insecure
434 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800435
436 Note that we ask permission for each individual hook even though we use
437 the hash of all hooks when detecting changes. We'd like the user to be
438 able to approve / deny each hook individually. We only use the hash of all
439 hooks because there is no other easy way to detect changes to local imports.
440
441 Returns:
442 True if this hook is approved to run; False otherwise.
443
444 Raises:
445 HookError: Raised if the user doesn't approve and abort_if_user_denies
446 was passed to the consturctor.
447 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400448 if self._ManifestUrlHasSecureScheme():
449 return self._CheckForHookApprovalManifest()
450 else:
451 return self._CheckForHookApprovalHash()
452
453 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
454 changed_prompt):
455 """Check for approval for a particular attribute and hook.
456
457 Args:
458 subkey: The git config key under [repo.hooks.<hook_type>] to store the
459 last approved string.
460 new_val: The new value to compare against the last approved one.
461 main_prompt: Message to display to the user to ask for approval.
462 changed_prompt: Message explaining why we're re-asking for approval.
463
464 Returns:
465 True if this hook is approved to run; False otherwise.
466
467 Raises:
468 HookError: Raised if the user doesn't approve and abort_if_user_denies
469 was passed to the consturctor.
470 """
Doug Anderson37282b42011-03-04 11:54:18 -0800471 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800473
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 # Get the last value that the user approved for this hook; may be None.
475 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800476
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800478 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400479 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800480 # Approval matched. We're done.
481 return True
482 else:
483 # Give the user a reason why we're prompting, since they last told
484 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400485 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800486 else:
487 prompt = ''
488
489 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
490 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400491 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530492 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900493 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800494
495 # User is doing a one-time approval.
496 if response in ('y', 'yes'):
497 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400498 elif response == 'always':
499 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800500 return True
501
502 # For anything else, we'll assume no approval.
503 if self._abort_if_user_denies:
504 raise HookError('You must allow the %s hook or use --no-verify.' %
505 self._hook_type)
506
507 return False
508
Mike Frysinger40252c22016-08-15 21:23:44 -0400509 def _ManifestUrlHasSecureScheme(self):
510 """Check if the URI for the manifest is a secure transport."""
511 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
512 parse_results = urllib.parse.urlparse(self._manifest_url)
513 return parse_results.scheme in secure_schemes
514
515 def _CheckForHookApprovalManifest(self):
516 """Check whether the user has approved this manifest host.
517
518 Returns:
519 True if this hook is approved to run; False otherwise.
520 """
521 return self._CheckForHookApprovalHelper(
522 'approvedmanifest',
523 self._manifest_url,
524 'Run hook scripts from %s' % (self._manifest_url,),
525 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
526
527 def _CheckForHookApprovalHash(self):
528 """Check whether the user has approved the hooks repo.
529
530 Returns:
531 True if this hook is approved to run; False otherwise.
532 """
533 prompt = ('Repo %s run the script:\n'
534 ' %s\n'
535 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700536 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400537 return self._CheckForHookApprovalHelper(
538 'approvedhash',
539 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700540 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400541 'Scripts have changed since %s was allowed.' % (self._hook_type,))
542
Doug Anderson37282b42011-03-04 11:54:18 -0800543 def _ExecuteHook(self, **kwargs):
544 """Actually execute the given hook.
545
546 This will run the hook's 'main' function in our python interpreter.
547
548 Args:
549 kwargs: Keyword arguments to pass to the hook. These are often specific
550 to the hook type. For instance, pre-upload hooks will contain
551 a project_list.
552 """
553 # Keep sys.path and CWD stashed away so that we can always restore them
554 # upon function exit.
555 orig_path = os.getcwd()
556 orig_syspath = sys.path
557
558 try:
559 # Always run hooks with CWD as topdir.
560 os.chdir(self._topdir)
561
562 # Put the hook dir as the first item of sys.path so hooks can do
563 # relative imports. We want to replace the repo dir as [0] so
564 # hooks can't import repo files.
565 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
566
567 # Exec, storing global context in the context dict. We catch exceptions
568 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500569 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800570 try:
Anthony King70f68902014-05-05 21:15:34 +0100571 exec(compile(open(self._script_fullpath).read(),
572 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800573 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700574 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
575 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800576
577 # Running the script should have defined a main() function.
578 if 'main' not in context:
579 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
580
Doug Anderson37282b42011-03-04 11:54:18 -0800581 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
582 # We don't actually want hooks to define their main with this argument--
583 # it's there to remind them that their hook should always take **kwargs.
584 # For instance, a pre-upload hook should be defined like:
585 # def main(project_list, **kwargs):
586 #
587 # This allows us to later expand the API without breaking old hooks.
588 kwargs = kwargs.copy()
589 kwargs['hook_should_take_kwargs'] = True
590
591 # Call the main function in the hook. If the hook should cause the
592 # build to fail, it will raise an Exception. We'll catch that convert
593 # to a HookError w/ just the failing traceback.
594 try:
595 context['main'](**kwargs)
596 except Exception:
597 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700598 'above.' % (traceback.format_exc(),
599 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800600 finally:
601 # Restore sys.path and CWD.
602 sys.path = orig_syspath
603 os.chdir(orig_path)
604
605 def Run(self, user_allows_all_hooks, **kwargs):
606 """Run the hook.
607
608 If the hook doesn't exist (because there is no hooks project or because
609 this particular hook is not enabled), this is a no-op.
610
611 Args:
612 user_allows_all_hooks: If True, we will never prompt about running the
613 hook--we'll just assume it's OK to run it.
614 kwargs: Keyword arguments to pass to the hook. These are often specific
615 to the hook type. For instance, pre-upload hooks will contain
616 a project_list.
617
618 Raises:
619 HookError: If there was a problem finding the hook or the user declined
620 to run a required hook (from _CheckForHookApproval).
621 """
622 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700623 if ((not self._hooks_project) or (self._hook_type not in
624 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800625 return
626
627 # Bail with a nice error if we can't find the hook.
628 if not os.path.isfile(self._script_fullpath):
629 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
630
631 # Make sure the user is OK with running the hook.
632 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
633 return
634
635 # Run the hook with the same version of python we're using.
636 self._ExecuteHook(**kwargs)
637
638
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600640 # These objects can be shared between several working trees.
641 shareable_files = ['description', 'info']
642 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
643 # These objects can only be used by a single working tree.
644 working_tree_files = ['config', 'packed-refs', 'shallow']
645 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 def __init__(self,
648 manifest,
649 name,
650 remote,
651 gitdir,
David James8d201162013-10-11 17:03:19 -0700652 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 worktree,
654 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700655 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800656 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100657 rebase=True,
658 groups=None,
659 sync_c=False,
660 sync_s=False,
661 clone_depth=None,
662 upstream=None,
663 parent=None,
664 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900665 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700666 optimized_fetch=False,
667 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800668 """Init a Project object.
669
670 Args:
671 manifest: The XmlManifest object.
672 name: The `name` attribute of manifest.xml's project element.
673 remote: RemoteSpec object specifying its remote's properties.
674 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700675 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800676 worktree: Absolute path of git working tree.
677 relpath: Relative path of git working tree to repo's top directory.
678 revisionExpr: The `revision` attribute of manifest.xml's project element.
679 revisionId: git commit id for checking out.
680 rebase: The `rebase` attribute of manifest.xml's project element.
681 groups: The `groups` attribute of manifest.xml's project element.
682 sync_c: The `sync-c` attribute of manifest.xml's project element.
683 sync_s: The `sync-s` attribute of manifest.xml's project element.
684 upstream: The `upstream` attribute of manifest.xml's project element.
685 parent: The parent Project object.
686 is_derived: False if the project was explicitly defined in the manifest;
687 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400688 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900689 optimized_fetch: If True, when a project is set to a sha1 revision, only
690 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700691 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800692 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693 self.manifest = manifest
694 self.name = name
695 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800696 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700697 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800698 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700699 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800700 else:
701 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700703 self.revisionExpr = revisionExpr
704
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700705 if revisionId is None \
706 and revisionExpr \
707 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700708 self.revisionId = revisionExpr
709 else:
710 self.revisionId = revisionId
711
Mike Pontillod3153822012-02-28 11:53:24 -0800712 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700713 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700714 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800715 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900716 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700717 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800718 self.parent = parent
719 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900720 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800721 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800722
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500725 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500726 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700727 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
728 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800730 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700731 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800732 else:
733 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700734 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700735 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700736 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400737 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700738 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739
Doug Anderson37282b42011-03-04 11:54:18 -0800740 # This will be filled in if a project is later identified to be the
741 # project containing repo hooks.
742 self.enabled_repo_hooks = []
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800745 def Derived(self):
746 return self.is_derived
747
748 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600750 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751
752 @property
753 def CurrentBranch(self):
754 """Obtain the name of the currently checked out branch.
755 The branch name omits the 'refs/heads/' prefix.
756 None is returned if the project is on a detached HEAD.
757 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700758 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 if b.startswith(R_HEADS):
760 return b[len(R_HEADS):]
761 return None
762
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700763 def IsRebaseInProgress(self):
764 w = self.worktree
765 g = os.path.join(w, '.git')
766 return os.path.exists(os.path.join(g, 'rebase-apply')) \
767 or os.path.exists(os.path.join(g, 'rebase-merge')) \
768 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200769
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 def IsDirty(self, consider_untracked=True):
771 """Is the working directory modified in some way?
772 """
773 self.work_git.update_index('-q',
774 '--unmerged',
775 '--ignore-missing',
776 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900777 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 return True
779 if self.work_git.DiffZ('diff-files'):
780 return True
781 if consider_untracked and self.work_git.LsOthers():
782 return True
783 return False
784
785 _userident_name = None
786 _userident_email = None
787
788 @property
789 def UserName(self):
790 """Obtain the user's personal name.
791 """
792 if self._userident_name is None:
793 self._LoadUserIdentity()
794 return self._userident_name
795
796 @property
797 def UserEmail(self):
798 """Obtain the user's email address. This is very likely
799 to be their Gerrit login.
800 """
801 if self._userident_email is None:
802 self._LoadUserIdentity()
803 return self._userident_email
804
805 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900806 u = self.bare_git.var('GIT_COMMITTER_IDENT')
807 m = re.compile("^(.*) <([^>]*)> ").match(u)
808 if m:
809 self._userident_name = m.group(1)
810 self._userident_email = m.group(2)
811 else:
812 self._userident_name = ''
813 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814
815 def GetRemote(self, name):
816 """Get the configuration for a single remote.
817 """
818 return self.config.GetRemote(name)
819
820 def GetBranch(self, name):
821 """Get the configuration for a single branch.
822 """
823 return self.config.GetBranch(name)
824
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700825 def GetBranches(self):
826 """Get all existing local branches.
827 """
828 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900829 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700830 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530832 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700833 if name.startswith(R_HEADS):
834 name = name[len(R_HEADS):]
835 b = self.GetBranch(name)
836 b.current = name == current
837 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900838 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700839 heads[name] = b
840
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530841 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700842 if name.startswith(R_PUB):
843 name = name[len(R_PUB):]
844 b = heads.get(name)
845 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900846 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700847
848 return heads
849
Colin Cross5acde752012-03-28 20:15:45 -0700850 def MatchesGroups(self, manifest_groups):
851 """Returns true if the manifest groups specified at init should cause
852 this project to be synced.
853 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700854 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700855
856 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700857 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700858 manifest_groups: "-group1,group2"
859 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500860
861 The special manifest group "default" will match any project that
862 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700863 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500864 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700865 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700866 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500867 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700868
Conley Owens971de8e2012-04-16 10:36:08 -0700869 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700870 for group in expanded_manifest_groups:
871 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700872 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700873 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700874 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700875
Conley Owens971de8e2012-04-16 10:36:08 -0700876 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700878# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700879 def UncommitedFiles(self, get_all=True):
880 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700882 Args:
883 get_all: a boolean, if True - get information about all different
884 uncommitted files. If False - return as soon as any kind of
885 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500886 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700887 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500888 self.work_git.update_index('-q',
889 '--unmerged',
890 '--ignore-missing',
891 '--refresh')
892 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700893 details.append("rebase in progress")
894 if not get_all:
895 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500896
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700897 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
898 if changes:
899 details.extend(changes)
900 if not get_all:
901 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500902
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700903 changes = self.work_git.DiffZ('diff-files').keys()
904 if changes:
905 details.extend(changes)
906 if not get_all:
907 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500908
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700909 changes = self.work_git.LsOthers()
910 if changes:
911 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500912
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700913 return details
914
915 def HasChanges(self):
916 """Returns true if there are uncommitted changes.
917 """
918 if self.UncommitedFiles(get_all=False):
919 return True
920 else:
921 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500922
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600923 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200925
926 Args:
927 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600928 quiet: If True then only print the project name. Do not print
929 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 """
931 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700932 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200933 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700934 print(file=output_redir)
935 print('project %s/' % self.relpath, file=output_redir)
936 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 return
938
939 self.work_git.update_index('-q',
940 '--unmerged',
941 '--ignore-missing',
942 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700943 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
945 df = self.work_git.DiffZ('diff-files')
946 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100947 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700948 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949
950 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700951 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200952 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700953 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600955 if quiet:
956 out.nl()
957 return 'DIRTY'
958
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959 branch = self.CurrentBranch
960 if branch is None:
961 out.nobranch('(*** NO BRANCH ***)')
962 else:
963 out.branch('branch %s', branch)
964 out.nl()
965
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700966 if rb:
967 out.important('prior sync failed; rebase still in progress')
968 out.nl()
969
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 paths = list()
971 paths.extend(di.keys())
972 paths.extend(df.keys())
973 paths.extend(do)
974
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530975 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900976 try:
977 i = di[p]
978 except KeyError:
979 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900981 try:
982 f = df[p]
983 except KeyError:
984 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200985
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900986 if i:
987 i_status = i.status.upper()
988 else:
989 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900991 if f:
992 f_status = f.status.lower()
993 else:
994 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995
996 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800997 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700998 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 else:
1000 line = ' %s%s\t%s' % (i_status, f_status, p)
1001
1002 if i and not f:
1003 out.added('%s', line)
1004 elif (i and f) or (not i and f):
1005 out.changed('%s', line)
1006 elif not i and not f:
1007 out.untracked('%s', line)
1008 else:
1009 out.write('%s', line)
1010 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001011
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001012 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013
pelyad67872d2012-03-28 14:49:58 +03001014 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 """Prints the status of the repository to stdout.
1016 """
1017 out = DiffColoring(self.config)
1018 cmd = ['diff']
1019 if out.is_on:
1020 cmd.append('--color')
1021 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001022 if absolute_paths:
1023 cmd.append('--src-prefix=a/%s/' % self.relpath)
1024 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025 cmd.append('--')
1026 p = GitCommand(self,
1027 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001028 capture_stdout=True,
1029 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030 has_diff = False
1031 for line in p.process.stdout:
1032 if not has_diff:
1033 out.nl()
1034 out.project('project %s/' % self.relpath)
1035 out.nl()
1036 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001037 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 p.Wait()
1039
1040
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001041# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 """Was the branch published (uploaded) for code review?
1045 If so, returns the SHA-1 hash of the last published
1046 state for the branch.
1047 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001048 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001050 try:
1051 return self.bare_git.rev_parse(key)
1052 except GitError:
1053 return None
1054 else:
1055 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001056 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001057 except KeyError:
1058 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059
David Pursehouse8a68ff92012-09-24 12:15:13 +09001060 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 """Prunes any stale published refs.
1062 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 if all_refs is None:
1064 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 heads = set()
1066 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301067 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 if name.startswith(R_HEADS):
1069 heads.add(name)
1070 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301073 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 n = name[len(R_PUB):]
1075 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001076 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001078 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 """List any branches which can be uploaded for review.
1080 """
1081 heads = {}
1082 pubed = {}
1083
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301084 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001086 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001088 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
1090 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301091 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001092 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001094 if selected_branch and branch != selected_branch:
1095 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001097 rb = self.GetUploadableBranch(branch)
1098 if rb:
1099 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 return ready
1101
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001102 def GetUploadableBranch(self, branch_name):
1103 """Get a single uploadable branch, or None.
1104 """
1105 branch = self.GetBranch(branch_name)
1106 base = branch.LocalMerge
1107 if branch.LocalMerge:
1108 rb = ReviewableBranch(self, branch, base)
1109 if rb.commits:
1110 return rb
1111 return None
1112
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001113 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001114 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001115 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001116 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001117 private=False,
Vadim Bendebury329651b2018-10-31 13:48:01 -07001118 notify=None,
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001119 wip=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001120 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 """Uploads the named branch for code review.
1122 """
1123 if branch is None:
1124 branch = self.CurrentBranch
1125 if branch is None:
1126 raise GitError('not currently on a branch')
1127
1128 branch = self.GetBranch(branch)
1129 if not branch.LocalMerge:
1130 raise GitError('branch %s does not track a remote' % branch.name)
1131 if not branch.remote.review:
1132 raise GitError('remote %s has no review url' % branch.remote.name)
1133
Bryan Jacobsf609f912013-05-06 13:36:24 -04001134 if dest_branch is None:
1135 dest_branch = self.dest_branch
1136 if dest_branch is None:
1137 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138 if not dest_branch.startswith(R_HEADS):
1139 dest_branch = R_HEADS + dest_branch
1140
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001141 if not branch.remote.projectname:
1142 branch.remote.projectname = self.name
1143 branch.remote.Save()
1144
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001145 url = branch.remote.ReviewUrl(self.UserEmail)
1146 if url is None:
1147 raise UploadError('review not configured')
1148 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001149
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001150 if url.startswith('ssh://'):
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001151 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001152
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001153 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001154
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001155 if dest_branch.startswith(R_HEADS):
1156 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001157
1158 upload_type = 'for'
1159 if draft:
1160 upload_type = 'drafts'
1161
1162 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1163 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001164 if auto_topic:
1165 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001166
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001167 opts = ['r=%s' % p for p in people[0]]
1168 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendebury329651b2018-10-31 13:48:01 -07001169 if notify:
1170 opts += ['notify=' + notify]
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001171 if private:
1172 opts += ['private']
1173 if wip:
1174 opts += ['wip']
1175 if opts:
1176 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001177 cmd.append(ref_spec)
1178
Anthony King7bdac712014-07-16 12:56:40 +01001179 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001180 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
1182 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1183 self.bare_git.UpdateRef(R_PUB + branch.name,
1184 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001185 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186
1187
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001188# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
Julien Campergue335f5ef2013-10-16 11:02:35 +02001190 def _ExtractArchive(self, tarpath, path=None):
1191 """Extract the given tar on its current location
1192
1193 Args:
1194 - tarpath: The path to the actual tar file
1195
1196 """
1197 try:
1198 with tarfile.open(tarpath, 'r') as tar:
1199 tar.extractall(path=path)
1200 return True
1201 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001202 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001203 return False
1204
Ningning Xiac2fbc782016-08-22 14:24:39 -07001205 def CachePopulate(self, cache_dir, url):
1206 """Populate cache in the cache_dir.
1207
1208 Args:
1209 cache_dir: Directory to cache git files from Google Storage.
1210 url: Git url of current repository.
1211
1212 Raises:
1213 CacheApplyError if it fails to populate the git cache.
1214 """
1215 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1216 '--cache-dir', cache_dir, url]
1217
1218 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1219 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1220 'url: %s' % (cache_dir, url))
1221
1222 def CacheExists(self, cache_dir, url):
1223 """Check the existence of the cache files.
1224
1225 Args:
1226 cache_dir: Directory to cache git files.
1227 url: Git url of current repository.
1228
1229 Raises:
1230 CacheApplyError if the cache files do not exist.
1231 """
1232 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1233
1234 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1235 if exist.Wait() != 0:
1236 raise CacheApplyError('Failed to execute git cache exists cmd. '
1237 'cache_dir: %s url: %s' % (cache_dir, url))
1238
1239 if not exist.stdout or not exist.stdout.strip():
1240 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1241 'url: %s' % (cache_dir, url))
1242 return exist.stdout.strip()
1243
1244 def CacheApply(self, cache_dir):
1245 """Apply git cache files populated from Google Storage buckets.
1246
1247 Args:
1248 cache_dir: Directory to cache git files.
1249
1250 Raises:
1251 CacheApplyError if it fails to apply git caches.
1252 """
1253 remote = self.GetRemote(self.remote.name)
1254
1255 self.CachePopulate(cache_dir, remote.url)
1256
1257 mirror_dir = self.CacheExists(cache_dir, remote.url)
1258
1259 refspec = RefSpec(True, 'refs/heads/*',
1260 'refs/remotes/%s/*' % remote.name)
1261
1262 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1263 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1264 raise CacheApplyError('Failed to fetch refs %s from %s' %
1265 (mirror_dir, str(refspec)))
1266
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001267 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001268 quiet=False,
1269 is_new=None,
1270 current_branch_only=False,
1271 force_sync=False,
1272 clone_bundle=True,
1273 no_tags=False,
1274 archive=False,
1275 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001276 prune=False,
Mike Frysingerde72f6a2018-12-13 03:20:31 -05001277 submodules=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001278 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 """Perform only the network IO portion of the sync process.
1280 Local working directory/branch state is not affected.
1281 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001282 if archive and not isinstance(self, MetaProject):
1283 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001284 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001285 return False
1286
1287 name = self.relpath.replace('\\', '/')
1288 name = name.replace('/', '_')
1289 tarpath = '%s.tar' % name
1290 topdir = self.manifest.topdir
1291
1292 try:
1293 self._FetchArchive(tarpath, cwd=topdir)
1294 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001295 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001296 return False
1297
1298 # From now on, we only need absolute tarpath
1299 tarpath = os.path.join(topdir, tarpath)
1300
1301 if not self._ExtractArchive(tarpath, path=topdir):
1302 return False
1303 try:
1304 os.remove(tarpath)
1305 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001306 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001307 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001308 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001309 if is_new is None:
1310 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001311 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001312 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001313 else:
1314 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001316
1317 if is_new:
1318 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1319 try:
1320 fd = open(alt, 'rb')
1321 try:
1322 alt_dir = fd.readline().rstrip()
1323 finally:
1324 fd.close()
1325 except IOError:
1326 alt_dir = None
1327 else:
1328 alt_dir = None
1329
Ningning Xiac2fbc782016-08-22 14:24:39 -07001330 applied_cache = False
1331 # If cache_dir is provided, and it's a new repository without
1332 # alternative_dir, bootstrap this project repo with the git
1333 # cache files.
1334 if cache_dir is not None and is_new and alt_dir is None:
1335 try:
1336 self.CacheApply(cache_dir)
1337 applied_cache = True
1338 is_new = False
1339 except CacheApplyError as e:
1340 _error('Could not apply git cache: %s', e)
1341 _error('Please check if you have the right GS credentials.')
1342 _error('Please check if the cache files exist in GS.')
1343
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001344 if clone_bundle \
Ningning Xiac2fbc782016-08-22 14:24:39 -07001345 and not applied_cache \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001346 and alt_dir is None \
1347 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001348 is_new = False
1349
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001350 if not current_branch_only:
1351 if self.sync_c:
1352 current_branch_only = True
1353 elif not self.manifest._loaded:
1354 # Manifest cannot check defaults until it syncs.
1355 current_branch_only = False
1356 elif self.manifest.default.sync_c:
1357 current_branch_only = True
1358
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001359 if self.clone_depth:
1360 depth = self.clone_depth
1361 else:
1362 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1363
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001364 need_to_fetch = not (optimized_fetch and
1365 (ID_RE.match(self.revisionExpr) and
1366 self._CheckForSha1()))
1367 if (need_to_fetch and
1368 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1369 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001370 no_tags=no_tags, prune=prune, depth=depth,
1371 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001372 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001373
1374 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001375 self._InitMRef()
1376 else:
1377 self._InitMirrorHead()
1378 try:
1379 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1380 except OSError:
1381 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001383
1384 def PostRepoUpgrade(self):
1385 self._InitHooks()
1386
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001387 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001388 if self.manifest.isGitcClient:
1389 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001390 for copyfile in self.copyfiles:
1391 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001392 for linkfile in self.linkfiles:
1393 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394
Julien Camperguedd654222014-01-09 16:21:37 +01001395 def GetCommitRevisionId(self):
1396 """Get revisionId of a commit.
1397
1398 Use this method instead of GetRevisionId to get the id of the commit rather
1399 than the id of the current git object (for example, a tag)
1400
1401 """
1402 if not self.revisionExpr.startswith(R_TAGS):
1403 return self.GetRevisionId(self._allrefs)
1404
1405 try:
1406 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1407 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001408 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1409 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001410
David Pursehouse8a68ff92012-09-24 12:15:13 +09001411 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001412 if self.revisionId:
1413 return self.revisionId
1414
1415 rem = self.GetRemote(self.remote.name)
1416 rev = rem.ToLocal(self.revisionExpr)
1417
David Pursehouse8a68ff92012-09-24 12:15:13 +09001418 if all_refs is not None and rev in all_refs:
1419 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001420
1421 try:
1422 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1423 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001424 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1425 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001426
Martin Kellye4e94d22017-03-21 16:05:12 -07001427 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428 """Perform only the local IO portion of the sync process.
1429 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001431 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001432 all_refs = self.bare_ref.all
1433 self.CleanPublishedCache(all_refs)
1434 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001435
David Pursehouse1d947b32012-10-25 12:23:11 +09001436 def _doff():
1437 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001438 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001439
Martin Kellye4e94d22017-03-21 16:05:12 -07001440 def _dosubmodules():
1441 self._SyncSubmodules(quiet=True)
1442
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001443 head = self.work_git.GetHead()
1444 if head.startswith(R_HEADS):
1445 branch = head[len(R_HEADS):]
1446 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001447 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001448 except KeyError:
1449 head = None
1450 else:
1451 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001452
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001453 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001454 # Currently on a detached HEAD. The user is assumed to
1455 # not have any local modifications worth worrying about.
1456 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001457 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001458 syncbuf.fail(self, _PriorSyncFailedError())
1459 return
1460
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001461 if head == revid:
1462 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001463 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001464 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001465 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001466 # The copy/linkfile config may have changed.
1467 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001468 return
1469 else:
1470 lost = self._revlist(not_rev(revid), HEAD)
1471 if lost:
1472 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001473
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001475 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001476 if submodules:
1477 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001478 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001479 syncbuf.fail(self, e)
1480 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001481 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001482 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001484 if head == revid:
1485 # No changes; don't do anything further.
1486 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001487 # The copy/linkfile config may have changed.
1488 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001489 return
1490
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001493 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001495 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001496 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001497 syncbuf.info(self,
1498 "leaving %s; does not track upstream",
1499 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001501 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001502 if submodules:
1503 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001504 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001505 syncbuf.fail(self, e)
1506 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001507 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001508 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001509
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001510 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001511 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001513 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514 if not_merged:
1515 if upstream_gain:
1516 # The user has published this branch and some of those
1517 # commits are not yet merged upstream. We do not want
1518 # to rewrite the published commits so we punt.
1519 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001520 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001521 "branch %s is published (but not merged) and is now "
1522 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001523 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001524 elif pub == head:
1525 # All published commits are merged, and thus we are a
1526 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001527 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001528 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001529 if submodules:
1530 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001531 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001533 # Examine the local commits not in the remote. Find the
1534 # last one attributed to this user, if any.
1535 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001536 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001537 last_mine = None
1538 cnt_mine = 0
1539 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301540 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001541 if committer_email == self.UserEmail:
1542 last_mine = commit_id
1543 cnt_mine += 1
1544
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001545 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001546 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001547
1548 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001549 syncbuf.fail(self, _DirtyError())
1550 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001552 # If the upstream switched on us, warn the user.
1553 #
1554 if branch.merge != self.revisionExpr:
1555 if branch.merge and self.revisionExpr:
1556 syncbuf.info(self,
1557 'manifest switched %s...%s',
1558 branch.merge,
1559 self.revisionExpr)
1560 elif branch.merge:
1561 syncbuf.info(self,
1562 'manifest no longer tracks %s',
1563 branch.merge)
1564
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001565 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001567 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001568 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001569 syncbuf.info(self,
1570 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001571 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001573 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001574 if not ID_RE.match(self.revisionExpr):
1575 # in case of manifest sync the revisionExpr might be a SHA1
1576 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001577 if not branch.merge.startswith('refs/'):
1578 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001579 branch.Save()
1580
Mike Pontillod3153822012-02-28 11:53:24 -08001581 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001582 def _docopyandlink():
1583 self._CopyAndLinkFiles()
1584
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001585 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001586 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001587 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001588 if submodules:
1589 syncbuf.later2(self, _dosubmodules)
1590 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001591 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001593 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001594 if submodules:
1595 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001596 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001597 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001598 syncbuf.fail(self, e)
1599 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001601 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001602 if submodules:
1603 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001605 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606 # dest should already be an absolute path, but src is project relative
1607 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001608 abssrc = os.path.join(self.worktree, src)
1609 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001611 def AddLinkFile(self, src, dest, absdest):
1612 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001613 # make src relative path to dest
1614 absdestdir = os.path.dirname(absdest)
1615 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001616 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001617
James W. Mills24c13082012-04-12 15:04:13 -05001618 def AddAnnotation(self, name, value, keep):
1619 self.annotations.append(_Annotation(name, value, keep))
1620
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001621 def DownloadPatchSet(self, change_id, patch_id):
1622 """Download a single patch set of a single change to FETCH_HEAD.
1623 """
1624 remote = self.GetRemote(self.remote.name)
1625
1626 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001627 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001628 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001629 if GitCommand(self, cmd, bare=True).Wait() != 0:
1630 return None
1631 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001632 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001633 change_id,
1634 patch_id,
1635 self.bare_git.rev_parse('FETCH_HEAD'))
1636
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001637
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001638# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639
Simran Basib9a1b732015-08-20 12:19:28 -07001640 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 """Create a new branch off the manifest's revision.
1642 """
Simran Basib9a1b732015-08-20 12:19:28 -07001643 if not branch_merge:
1644 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001645 head = self.work_git.GetHead()
1646 if head == (R_HEADS + name):
1647 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648
David Pursehouse8a68ff92012-09-24 12:15:13 +09001649 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001650 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001651 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001652 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001653 capture_stdout=True,
1654 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001655
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001656 branch = self.GetBranch(name)
1657 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001658 branch.merge = branch_merge
1659 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1660 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001661 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001662
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001663 if head.startswith(R_HEADS):
1664 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001665 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001666 except KeyError:
1667 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001668 if revid and head and revid == head:
1669 ref = os.path.join(self.gitdir, R_HEADS + name)
1670 try:
1671 os.makedirs(os.path.dirname(ref))
1672 except OSError:
1673 pass
1674 _lwrite(ref, '%s\n' % revid)
1675 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1676 'ref: %s%s\n' % (R_HEADS, name))
1677 branch.Save()
1678 return True
1679
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001680 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001681 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001682 capture_stdout=True,
1683 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001684 branch.Save()
1685 return True
1686 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687
Wink Saville02d79452009-04-10 13:01:24 -07001688 def CheckoutBranch(self, name):
1689 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001690
1691 Args:
1692 name: The name of the branch to checkout.
1693
1694 Returns:
1695 True if the checkout succeeded; False if it didn't; None if the branch
1696 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001697 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001698 rev = R_HEADS + name
1699 head = self.work_git.GetHead()
1700 if head == rev:
1701 # Already on the branch
1702 #
1703 return True
Wink Saville02d79452009-04-10 13:01:24 -07001704
David Pursehouse8a68ff92012-09-24 12:15:13 +09001705 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001706 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001707 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001708 except KeyError:
1709 # Branch does not exist in this project
1710 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001711 return None
Wink Saville02d79452009-04-10 13:01:24 -07001712
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001713 if head.startswith(R_HEADS):
1714 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001715 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001716 except KeyError:
1717 head = None
1718
1719 if head == revid:
1720 # Same revision; just update HEAD to point to the new
1721 # target branch, but otherwise take no other action.
1722 #
1723 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1724 'ref: %s%s\n' % (R_HEADS, name))
1725 return True
1726
1727 return GitCommand(self,
1728 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001729 capture_stdout=True,
1730 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001731
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001732 def AbandonBranch(self, name):
1733 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001734
1735 Args:
1736 name: The name of the branch to abandon.
1737
1738 Returns:
1739 True if the abandon succeeded; False if it didn't; None if the branch
1740 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001741 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001742 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001743 all_refs = self.bare_ref.all
1744 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001745 # Doesn't exist
1746 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001747
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001748 head = self.work_git.GetHead()
1749 if head == rev:
1750 # We can't destroy the branch while we are sitting
1751 # on it. Switch to a detached HEAD.
1752 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001753 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001754
David Pursehouse8a68ff92012-09-24 12:15:13 +09001755 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001756 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001757 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1758 '%s\n' % revid)
1759 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001760 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001761
1762 return GitCommand(self,
1763 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001764 capture_stdout=True,
1765 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 def PruneHeads(self):
1768 """Prune any topic branches already merged into upstream.
1769 """
1770 cb = self.CurrentBranch
1771 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001772 left = self._allrefs
1773 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774 if name.startswith(R_HEADS):
1775 name = name[len(R_HEADS):]
1776 if cb is None or name != cb:
1777 kill.append(name)
1778
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001779 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 if cb is not None \
1781 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001782 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783 self.work_git.DetachHead(HEAD)
1784 kill.append(cb)
1785
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001787 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001788
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789 try:
1790 self.bare_git.DetachHead(rev)
1791
1792 b = ['branch', '-d']
1793 b.extend(kill)
1794 b = GitCommand(self, b, bare=True,
1795 capture_stdout=True,
1796 capture_stderr=True)
1797 b.Wait()
1798 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001799 if ID_RE.match(old):
1800 self.bare_git.DetachHead(old)
1801 else:
1802 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001803 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001805 for branch in kill:
1806 if (R_HEADS + branch) not in left:
1807 self.CleanPublishedCache()
1808 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809
1810 if cb and cb not in kill:
1811 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001812 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001813
1814 kept = []
1815 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001816 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817 branch = self.GetBranch(branch)
1818 base = branch.LocalMerge
1819 if not base:
1820 base = rev
1821 kept.append(ReviewableBranch(self, branch, base))
1822 return kept
1823
1824
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001825# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001826
1827 def GetRegisteredSubprojects(self):
1828 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001829
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001830 def rec(subprojects):
1831 if not subprojects:
1832 return
1833 result.extend(subprojects)
1834 for p in subprojects:
1835 rec(p.subprojects)
1836 rec(self.subprojects)
1837 return result
1838
1839 def _GetSubmodules(self):
1840 # Unfortunately we cannot call `git submodule status --recursive` here
1841 # because the working tree might not exist yet, and it cannot be used
1842 # without a working tree in its current implementation.
1843
1844 def get_submodules(gitdir, rev):
1845 # Parse .gitmodules for submodule sub_paths and sub_urls
1846 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1847 if not sub_paths:
1848 return []
1849 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1850 # revision of submodule repository
1851 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1852 submodules = []
1853 for sub_path, sub_url in zip(sub_paths, sub_urls):
1854 try:
1855 sub_rev = sub_revs[sub_path]
1856 except KeyError:
1857 # Ignore non-exist submodules
1858 continue
1859 submodules.append((sub_rev, sub_path, sub_url))
1860 return submodules
1861
1862 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1863 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001864
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001865 def parse_gitmodules(gitdir, rev):
1866 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1867 try:
Anthony King7bdac712014-07-16 12:56:40 +01001868 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1869 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001870 except GitError:
1871 return [], []
1872 if p.Wait() != 0:
1873 return [], []
1874
1875 gitmodules_lines = []
1876 fd, temp_gitmodules_path = tempfile.mkstemp()
1877 try:
1878 os.write(fd, p.stdout)
1879 os.close(fd)
1880 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001881 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1882 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001883 if p.Wait() != 0:
1884 return [], []
1885 gitmodules_lines = p.stdout.split('\n')
1886 except GitError:
1887 return [], []
1888 finally:
1889 os.remove(temp_gitmodules_path)
1890
1891 names = set()
1892 paths = {}
1893 urls = {}
1894 for line in gitmodules_lines:
1895 if not line:
1896 continue
1897 m = re_path.match(line)
1898 if m:
1899 names.add(m.group(1))
1900 paths[m.group(1)] = m.group(2)
1901 continue
1902 m = re_url.match(line)
1903 if m:
1904 names.add(m.group(1))
1905 urls[m.group(1)] = m.group(2)
1906 continue
1907 names = sorted(names)
1908 return ([paths.get(name, '') for name in names],
1909 [urls.get(name, '') for name in names])
1910
1911 def git_ls_tree(gitdir, rev, paths):
1912 cmd = ['ls-tree', rev, '--']
1913 cmd.extend(paths)
1914 try:
Anthony King7bdac712014-07-16 12:56:40 +01001915 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1916 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001917 except GitError:
1918 return []
1919 if p.Wait() != 0:
1920 return []
1921 objects = {}
1922 for line in p.stdout.split('\n'):
1923 if not line.strip():
1924 continue
1925 object_rev, object_path = line.split()[2:4]
1926 objects[object_path] = object_rev
1927 return objects
1928
1929 try:
1930 rev = self.GetRevisionId()
1931 except GitError:
1932 return []
1933 return get_submodules(self.gitdir, rev)
1934
1935 def GetDerivedSubprojects(self):
1936 result = []
1937 if not self.Exists:
1938 # If git repo does not exist yet, querying its submodules will
1939 # mess up its states; so return here.
1940 return result
1941 for rev, path, url in self._GetSubmodules():
1942 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001943 relpath, worktree, gitdir, objdir = \
1944 self.manifest.GetSubprojectPaths(self, name, path)
1945 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001946 if project:
1947 result.extend(project.GetDerivedSubprojects())
1948 continue
David James8d201162013-10-11 17:03:19 -07001949
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001950 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001951 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001952 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001953 review=self.remote.review,
1954 revision=self.remote.revision)
1955 subproject = Project(manifest=self.manifest,
1956 name=name,
1957 remote=remote,
1958 gitdir=gitdir,
1959 objdir=objdir,
1960 worktree=worktree,
1961 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001962 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001963 revisionId=rev,
1964 rebase=self.rebase,
1965 groups=self.groups,
1966 sync_c=self.sync_c,
1967 sync_s=self.sync_s,
1968 parent=self,
1969 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001970 result.append(subproject)
1971 result.extend(subproject.GetDerivedSubprojects())
1972 return result
1973
1974
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001975# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001976 def _CheckForSha1(self):
1977 try:
1978 # if revision (sha or tag) is not present then following function
1979 # throws an error.
1980 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1981 return True
1982 except GitError:
1983 # There is no such persistent revision. We have to fetch it.
1984 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001985
Julien Campergue335f5ef2013-10-16 11:02:35 +02001986 def _FetchArchive(self, tarpath, cwd=None):
1987 cmd = ['archive', '-v', '-o', tarpath]
1988 cmd.append('--remote=%s' % self.remote.url)
1989 cmd.append('--prefix=%s/' % self.relpath)
1990 cmd.append(self.revisionExpr)
1991
1992 command = GitCommand(self, cmd, cwd=cwd,
1993 capture_stdout=True,
1994 capture_stderr=True)
1995
1996 if command.Wait() != 0:
1997 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1998
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001999 def _RemoteFetch(self, name=None,
2000 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002001 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002002 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002003 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002004 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002005 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002006 depth=None,
2007 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002008
2009 is_sha1 = False
2010 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002011 # The depth should not be used when fetching to a mirror because
2012 # it will result in a shallow repository that cannot be cloned or
2013 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002014 # The repo project should also never be synced with partial depth.
2015 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2016 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002017
Shawn Pearce69e04d82014-01-29 12:48:54 -08002018 if depth:
2019 current_branch_only = True
2020
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002021 if ID_RE.match(self.revisionExpr) is not None:
2022 is_sha1 = True
2023
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002024 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002025 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002026 # this is a tag and its sha1 value should never change
2027 tag_name = self.revisionExpr[len(R_TAGS):]
2028
2029 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05002030 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002031 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002032 if is_sha1 and not depth:
2033 # When syncing a specific commit and --depth is not set:
2034 # * if upstream is explicitly specified and is not a sha1, fetch only
2035 # upstream as users expect only upstream to be fetch.
2036 # Note: The commit might not be in upstream in which case the sync
2037 # will fail.
2038 # * otherwise, fetch all branches to make sure we end up with the
2039 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002040 if self.upstream:
2041 current_branch_only = not ID_RE.match(self.upstream)
2042 else:
2043 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002044
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002045 if not name:
2046 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002047
2048 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002049 remote = self.GetRemote(name)
2050 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002051 ssh_proxy = True
2052
Shawn O. Pearce88443382010-10-08 10:02:09 +02002053 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002054 if alt_dir and 'objects' == os.path.basename(alt_dir):
2055 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2057 remote = self.GetRemote(name)
2058
David Pursehouse8a68ff92012-09-24 12:15:13 +09002059 all_refs = self.bare_ref.all
2060 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002061 tmp = set()
2062
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302063 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002064 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002065 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002066 all_refs[r] = ref_id
2067 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002068 continue
2069
David Pursehouse8a68ff92012-09-24 12:15:13 +09002070 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002071 continue
2072
David Pursehouse8a68ff92012-09-24 12:15:13 +09002073 r = 'refs/_alt/%s' % ref_id
2074 all_refs[r] = ref_id
2075 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002076 tmp.add(r)
2077
heping3d7bbc92017-04-12 19:51:47 +08002078 tmp_packed_lines = []
2079 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002080
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302081 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002082 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002083 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002084 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002085 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002086
heping3d7bbc92017-04-12 19:51:47 +08002087 tmp_packed = ''.join(tmp_packed_lines)
2088 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002089 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002090 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002091 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002092
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002093 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002094
Conley Owensf97e8382015-01-21 11:12:46 -08002095 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002096 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002097 else:
2098 # If this repo has shallow objects, then we don't know which refs have
2099 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2100 # do this with projects that don't have shallow objects, since it is less
2101 # efficient.
2102 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2103 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002104
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002105 if quiet:
2106 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002107 if not self.worktree:
2108 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002109 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002110
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002111 # If using depth then we should not get all the tags since they may
2112 # be outside of the depth.
2113 if no_tags or depth:
2114 cmd.append('--no-tags')
2115 else:
2116 cmd.append('--tags')
2117
David Pursehouse74cfd272015-10-14 10:50:15 +09002118 if prune:
2119 cmd.append('--prune')
2120
Martin Kellye4e94d22017-03-21 16:05:12 -07002121 if submodules:
2122 cmd.append('--recurse-submodules=on-demand')
2123
Conley Owens80b87fe2014-05-09 17:13:44 -07002124 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002125 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002126 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002127 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002128 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002129 spec.append('tag')
2130 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002131
David Pursehouse403b64e2015-04-27 10:41:33 +09002132 if not self.manifest.IsMirror:
2133 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002134 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002135 # Shallow checkout of a specific commit, fetch from that commit and not
2136 # the heads only as the commit might be deeper in the history.
2137 spec.append(branch)
2138 else:
2139 if is_sha1:
2140 branch = self.upstream
2141 if branch is not None and branch.strip():
2142 if not branch.startswith('refs/'):
2143 branch = R_HEADS + branch
2144 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002145 cmd.extend(spec)
2146
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002147 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002148 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002149 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002150 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002151 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002152 ok = True
2153 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002154 # If needed, run the 'git remote prune' the first time through the loop
2155 elif (not _i and
2156 "error:" in gitcmd.stderr and
2157 "git remote prune" in gitcmd.stderr):
2158 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002159 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002160 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002161 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002162 break
2163 continue
Brian Harring14a66742012-09-28 20:21:57 -07002164 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002165 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2166 # in sha1 mode, we just tried sync'ing from the upstream field; it
2167 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002168 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002169 elif ret < 0:
2170 # Git died with a signal, exit immediately
2171 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002172 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173
2174 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002175 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002176 if old_packed != '':
2177 _lwrite(packed_refs, old_packed)
2178 else:
2179 os.remove(packed_refs)
2180 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002181
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002182 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002183 # We just synced the upstream given branch; verify we
2184 # got what we wanted, else trigger a second run of all
2185 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002186 if not self._CheckForSha1():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002187 if current_branch_only and depth:
2188 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002189 return self._RemoteFetch(name=name,
2190 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002191 initial=False, quiet=quiet, alt_dir=alt_dir,
2192 depth=None)
2193 else:
2194 # Avoid infinite recursion: sync all branches with depth set to None
2195 return self._RemoteFetch(name=name, current_branch_only=False,
2196 initial=False, quiet=quiet, alt_dir=alt_dir,
2197 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002198
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002199 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002200
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002201 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002202 if initial and \
2203 (self.manifest.manifestProject.config.GetString('repo.depth') or
2204 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002205 return False
2206
2207 remote = self.GetRemote(self.remote.name)
2208 bundle_url = remote.url + '/clone.bundle'
2209 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002210 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2211 'persistent-http',
2212 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002213 return False
2214
2215 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2216 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2217
2218 exist_dst = os.path.exists(bundle_dst)
2219 exist_tmp = os.path.exists(bundle_tmp)
2220
2221 if not initial and not exist_dst and not exist_tmp:
2222 return False
2223
2224 if not exist_dst:
2225 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2226 if not exist_dst:
2227 return False
2228
2229 cmd = ['fetch']
2230 if quiet:
2231 cmd.append('--quiet')
2232 if not self.worktree:
2233 cmd.append('--update-head-ok')
2234 cmd.append(bundle_dst)
2235 for f in remote.fetch:
2236 cmd.append(str(f))
2237 cmd.append('refs/tags/*:refs/tags/*')
2238
2239 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002240 if os.path.exists(bundle_dst):
2241 os.remove(bundle_dst)
2242 if os.path.exists(bundle_tmp):
2243 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002244 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002245
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002246 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002247 if os.path.exists(dstPath):
2248 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002249
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002250 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002251 if quiet:
2252 cmd += ['--silent']
2253 if os.path.exists(tmpPath):
2254 size = os.stat(tmpPath).st_size
2255 if size >= 1024:
2256 cmd += ['--continue-at', '%d' % (size,)]
2257 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002258 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002259 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2260 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002261 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002262 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002263 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002264 if srcUrl.startswith('persistent-'):
2265 srcUrl = srcUrl[len('persistent-'):]
2266 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002267
Dave Borowitz137d0132015-01-02 11:12:54 -08002268 if IsTrace():
2269 Trace('%s', ' '.join(cmd))
2270 try:
2271 proc = subprocess.Popen(cmd)
2272 except OSError:
2273 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002274
Dave Borowitz137d0132015-01-02 11:12:54 -08002275 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002276
Dave Borowitz137d0132015-01-02 11:12:54 -08002277 if curlret == 22:
2278 # From curl man page:
2279 # 22: HTTP page not retrieved. The requested url was not found or
2280 # returned another error with the HTTP error code being 400 or above.
2281 # This return code only appears if -f, --fail is used.
2282 if not quiet:
2283 print("Server does not provide clone.bundle; ignoring.",
2284 file=sys.stderr)
2285 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002286
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002287 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002288 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002289 os.rename(tmpPath, dstPath)
2290 return True
2291 else:
2292 os.remove(tmpPath)
2293 return False
2294 else:
2295 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002296
Kris Giesingc8d882a2014-12-23 13:02:32 -08002297 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002298 try:
2299 with open(path) as f:
2300 if f.read(16) == '# v2 git bundle\n':
2301 return True
2302 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002303 if not quiet:
2304 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002305 return False
2306 except OSError:
2307 return False
2308
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002309 def _Checkout(self, rev, quiet=False):
2310 cmd = ['checkout']
2311 if quiet:
2312 cmd.append('-q')
2313 cmd.append(rev)
2314 cmd.append('--')
2315 if GitCommand(self, cmd).Wait() != 0:
2316 if self._allrefs:
2317 raise GitError('%s checkout %s ' % (self.name, rev))
2318
Anthony King7bdac712014-07-16 12:56:40 +01002319 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002320 cmd = ['cherry-pick']
2321 cmd.append(rev)
2322 cmd.append('--')
2323 if GitCommand(self, cmd).Wait() != 0:
2324 if self._allrefs:
2325 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2326
Anthony King7bdac712014-07-16 12:56:40 +01002327 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002328 cmd = ['revert']
2329 cmd.append('--no-edit')
2330 cmd.append(rev)
2331 cmd.append('--')
2332 if GitCommand(self, cmd).Wait() != 0:
2333 if self._allrefs:
2334 raise GitError('%s revert %s ' % (self.name, rev))
2335
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002336 def _ResetHard(self, rev, quiet=True):
2337 cmd = ['reset', '--hard']
2338 if quiet:
2339 cmd.append('-q')
2340 cmd.append(rev)
2341 if GitCommand(self, cmd).Wait() != 0:
2342 raise GitError('%s reset --hard %s ' % (self.name, rev))
2343
Martin Kellye4e94d22017-03-21 16:05:12 -07002344 def _SyncSubmodules(self, quiet=True):
2345 cmd = ['submodule', 'update', '--init', '--recursive']
2346 if quiet:
2347 cmd.append('-q')
2348 if GitCommand(self, cmd).Wait() != 0:
2349 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2350
Anthony King7bdac712014-07-16 12:56:40 +01002351 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002352 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002353 if onto is not None:
2354 cmd.extend(['--onto', onto])
2355 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002356 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357 raise GitError('%s rebase %s ' % (self.name, upstream))
2358
Pierre Tardy3d125942012-05-04 12:18:12 +02002359 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002360 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002361 if ffonly:
2362 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002363 if GitCommand(self, cmd).Wait() != 0:
2364 raise GitError('%s merge %s ' % (self.name, head))
2365
Kevin Degiabaa7f32014-11-12 11:27:45 -07002366 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002367 init_git_dir = not os.path.exists(self.gitdir)
2368 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002369 try:
2370 # Initialize the bare repository, which contains all of the objects.
2371 if init_obj_dir:
2372 os.makedirs(self.objdir)
2373 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002374
Kevin Degib1a07b82015-07-27 13:33:43 -06002375 # If we have a separate directory to hold refs, initialize it as well.
2376 if self.objdir != self.gitdir:
2377 if init_git_dir:
2378 os.makedirs(self.gitdir)
2379
2380 if init_obj_dir or init_git_dir:
2381 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2382 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002383 try:
2384 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2385 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002386 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002387 print("Retrying clone after deleting %s" %
2388 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002389 try:
2390 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002391 if self.worktree and os.path.exists(os.path.realpath
2392 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002393 shutil.rmtree(os.path.realpath(self.worktree))
2394 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2395 except:
2396 raise e
2397 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002398
Kevin Degi384b3c52014-10-16 16:02:58 -06002399 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002400 mp = self.manifest.manifestProject
2401 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002402
Kevin Degib1a07b82015-07-27 13:33:43 -06002403 if ref_dir or mirror_git:
2404 if not mirror_git:
2405 mirror_git = os.path.join(ref_dir, self.name + '.git')
2406 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2407 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002408
Kevin Degib1a07b82015-07-27 13:33:43 -06002409 if os.path.exists(mirror_git):
2410 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002411
Kevin Degib1a07b82015-07-27 13:33:43 -06002412 elif os.path.exists(repo_git):
2413 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002414
Kevin Degib1a07b82015-07-27 13:33:43 -06002415 else:
2416 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002417
Kevin Degib1a07b82015-07-27 13:33:43 -06002418 if ref_dir:
2419 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2420 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002421
Kevin Degib1a07b82015-07-27 13:33:43 -06002422 self._UpdateHooks()
2423
2424 m = self.manifest.manifestProject.config
2425 for key in ['user.name', 'user.email']:
2426 if m.Has(key, include_defaults=False):
2427 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002428 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002429 if self.manifest.IsMirror:
2430 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002431 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002432 self.config.SetString('core.bare', None)
2433 except Exception:
2434 if init_obj_dir and os.path.exists(self.objdir):
2435 shutil.rmtree(self.objdir)
2436 if init_git_dir and os.path.exists(self.gitdir):
2437 shutil.rmtree(self.gitdir)
2438 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439
Jimmie Westera0444582012-10-24 13:44:42 +02002440 def _UpdateHooks(self):
2441 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002442 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002443
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002444 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002445 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002446 if not os.path.exists(hooks):
2447 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002448 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002449 name = os.path.basename(stock_hook)
2450
Victor Boivie65e0f352011-04-18 11:23:29 +02002451 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002452 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002453 # Don't install a Gerrit Code Review hook if this
2454 # project does not appear to use it for reviews.
2455 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002456 # Since the manifest project is one of those, but also
2457 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002458 continue
2459
2460 dst = os.path.join(hooks, name)
2461 if os.path.islink(dst):
2462 continue
2463 if os.path.exists(dst):
2464 if filecmp.cmp(stock_hook, dst, shallow=False):
2465 os.remove(dst)
2466 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002467 _warn("%s: Not replacing locally modified %s hook",
2468 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002469 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002470 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002471 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002472 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002473 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002474 raise GitError('filesystem must support symlinks')
2475 else:
2476 raise
2477
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002478 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002479 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002481 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002482 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002483 remote.review = self.remote.review
2484 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002485
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002486 if self.worktree:
2487 remote.ResetFetch(mirror=False)
2488 else:
2489 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490 remote.Save()
2491
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002492 def _InitMRef(self):
2493 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002494 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002495
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002496 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002497 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002498
2499 def _InitAnyMRef(self, ref):
2500 cur = self.bare_ref.symref(ref)
2501
2502 if self.revisionId:
2503 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2504 msg = 'manifest set to %s' % self.revisionId
2505 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002506 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002507 else:
2508 remote = self.GetRemote(self.remote.name)
2509 dst = remote.ToLocal(self.revisionExpr)
2510 if cur != dst:
2511 msg = 'manifest set to %s' % self.revisionExpr
2512 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002513
Kevin Degi384b3c52014-10-16 16:02:58 -06002514 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002515 symlink_files = self.shareable_files[:]
2516 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002517 if share_refs:
2518 symlink_files += self.working_tree_files
2519 symlink_dirs += self.working_tree_dirs
2520 to_symlink = symlink_files + symlink_dirs
2521 for name in set(to_symlink):
2522 dst = os.path.realpath(os.path.join(destdir, name))
2523 if os.path.lexists(dst):
2524 src = os.path.realpath(os.path.join(srcdir, name))
2525 # Fail if the links are pointing to the wrong place
2526 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002527 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002528 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002529 'work tree. If you\'re comfortable with the '
2530 'possibility of losing the work tree\'s git metadata,'
2531 ' use `repo sync --force-sync {0}` to '
2532 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002533
David James8d201162013-10-11 17:03:19 -07002534 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2535 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2536
2537 Args:
2538 gitdir: The bare git repository. Must already be initialized.
2539 dotgit: The repository you would like to initialize.
2540 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2541 Only one work tree can store refs under a given |gitdir|.
2542 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2543 This saves you the effort of initializing |dotgit| yourself.
2544 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002545 symlink_files = self.shareable_files[:]
2546 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002547 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002548 symlink_files += self.working_tree_files
2549 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002550 to_symlink = symlink_files + symlink_dirs
2551
2552 to_copy = []
2553 if copy_all:
2554 to_copy = os.listdir(gitdir)
2555
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002556 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002557 for name in set(to_copy).union(to_symlink):
2558 try:
2559 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002560 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002561
Kevin Degi384b3c52014-10-16 16:02:58 -06002562 if os.path.lexists(dst):
2563 continue
David James8d201162013-10-11 17:03:19 -07002564
2565 # If the source dir doesn't exist, create an empty dir.
2566 if name in symlink_dirs and not os.path.lexists(src):
2567 os.makedirs(src)
2568
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002569 if name in to_symlink:
2570 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2571 elif copy_all and not os.path.islink(dst):
2572 if os.path.isdir(src):
2573 shutil.copytree(src, dst)
2574 elif os.path.isfile(src):
2575 shutil.copy(src, dst)
2576
Conley Owens80b87fe2014-05-09 17:13:44 -07002577 # If the source file doesn't exist, ensure the destination
2578 # file doesn't either.
2579 if name in symlink_files and not os.path.lexists(src):
2580 try:
2581 os.remove(dst)
2582 except OSError:
2583 pass
2584
David James8d201162013-10-11 17:03:19 -07002585 except OSError as e:
2586 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002587 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002588 else:
2589 raise
2590
Martin Kellye4e94d22017-03-21 16:05:12 -07002591 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002592 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002593 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002594 try:
2595 if init_dotgit:
2596 os.makedirs(dotgit)
2597 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2598 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002599
Kevin Degiabaa7f32014-11-12 11:27:45 -07002600 try:
2601 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2602 except GitError as e:
2603 if force_sync:
2604 try:
2605 shutil.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002606 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002607 except:
2608 raise e
2609 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002610
Kevin Degib1a07b82015-07-27 13:33:43 -06002611 if init_dotgit:
2612 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002613
Kevin Degib1a07b82015-07-27 13:33:43 -06002614 cmd = ['read-tree', '--reset', '-u']
2615 cmd.append('-v')
2616 cmd.append(HEAD)
2617 if GitCommand(self, cmd).Wait() != 0:
2618 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002619
Martin Kellye4e94d22017-03-21 16:05:12 -07002620 if submodules:
2621 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002622 self._CopyAndLinkFiles()
2623 except Exception:
2624 if init_dotgit:
2625 shutil.rmtree(dotgit)
2626 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002627
2628 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002629 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002631 def _revlist(self, *args, **kw):
2632 a = []
2633 a.extend(args)
2634 a.append('--')
2635 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636
2637 @property
2638 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002639 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002640
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002641 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002642 """Get logs between two revisions of this project."""
2643 comp = '..'
2644 if rev1:
2645 revs = [rev1]
2646 if rev2:
2647 revs.extend([comp, rev2])
2648 cmd = ['log', ''.join(revs)]
2649 out = DiffColoring(self.config)
2650 if out.is_on and color:
2651 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002652 if pretty_format is not None:
2653 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002654 if oneline:
2655 cmd.append('--oneline')
2656
2657 try:
2658 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2659 if log.Wait() == 0:
2660 return log.stdout
2661 except GitError:
2662 # worktree may not exist if groups changed for example. In that case,
2663 # try in gitdir instead.
2664 if not os.path.exists(self.worktree):
2665 return self.bare_git.log(*cmd[1:])
2666 else:
2667 raise
2668 return None
2669
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002670 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2671 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002672 """Get the list of logs from this revision to given revisionId"""
2673 logs = {}
2674 selfId = self.GetRevisionId(self._allrefs)
2675 toId = toProject.GetRevisionId(toProject._allrefs)
2676
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002677 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2678 pretty_format=pretty_format)
2679 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2680 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002681 return logs
2682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002683 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002684
David James8d201162013-10-11 17:03:19 -07002685 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 self._project = project
2687 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002688 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002690 def LsOthers(self):
2691 p = GitCommand(self._project,
2692 ['ls-files',
2693 '-z',
2694 '--others',
2695 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002696 bare=False,
David James8d201162013-10-11 17:03:19 -07002697 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002698 capture_stdout=True,
2699 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700 if p.Wait() == 0:
2701 out = p.stdout
2702 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002703 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002704 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002705 return []
2706
2707 def DiffZ(self, name, *args):
2708 cmd = [name]
2709 cmd.append('-z')
2710 cmd.extend(args)
2711 p = GitCommand(self._project,
2712 cmd,
David James8d201162013-10-11 17:03:19 -07002713 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002714 bare=False,
2715 capture_stdout=True,
2716 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002717 try:
2718 out = p.process.stdout.read()
2719 r = {}
2720 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002721 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002722 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002723 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002724 info = next(out)
2725 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002726 except StopIteration:
2727 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002728
2729 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002730
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731 def __init__(self, path, omode, nmode, oid, nid, state):
2732 self.path = path
2733 self.src_path = None
2734 self.old_mode = omode
2735 self.new_mode = nmode
2736 self.old_id = oid
2737 self.new_id = nid
2738
2739 if len(state) == 1:
2740 self.status = state
2741 self.level = None
2742 else:
2743 self.status = state[:1]
2744 self.level = state[1:]
2745 while self.level.startswith('0'):
2746 self.level = self.level[1:]
2747
2748 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002749 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750 if info.status in ('R', 'C'):
2751 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002752 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753 r[info.path] = info
2754 return r
2755 finally:
2756 p.Wait()
2757
2758 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002759 if self._bare:
2760 path = os.path.join(self._project.gitdir, HEAD)
2761 else:
2762 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002763 try:
2764 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002765 except IOError as e:
2766 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002767 try:
2768 line = fd.read()
2769 finally:
2770 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302771 try:
2772 line = line.decode()
2773 except AttributeError:
2774 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002775 if line.startswith('ref: '):
2776 return line[5:-1]
2777 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002778
2779 def SetHead(self, ref, message=None):
2780 cmdv = []
2781 if message is not None:
2782 cmdv.extend(['-m', message])
2783 cmdv.append(HEAD)
2784 cmdv.append(ref)
2785 self.symbolic_ref(*cmdv)
2786
2787 def DetachHead(self, new, message=None):
2788 cmdv = ['--no-deref']
2789 if message is not None:
2790 cmdv.extend(['-m', message])
2791 cmdv.append(HEAD)
2792 cmdv.append(new)
2793 self.update_ref(*cmdv)
2794
2795 def UpdateRef(self, name, new, old=None,
2796 message=None,
2797 detach=False):
2798 cmdv = []
2799 if message is not None:
2800 cmdv.extend(['-m', message])
2801 if detach:
2802 cmdv.append('--no-deref')
2803 cmdv.append(name)
2804 cmdv.append(new)
2805 if old is not None:
2806 cmdv.append(old)
2807 self.update_ref(*cmdv)
2808
2809 def DeleteRef(self, name, old=None):
2810 if not old:
2811 old = self.rev_parse(name)
2812 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002813 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002814
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002815 def rev_list(self, *args, **kw):
2816 if 'format' in kw:
2817 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2818 else:
2819 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002820 cmdv.extend(args)
2821 p = GitCommand(self._project,
2822 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002823 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002824 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002825 capture_stdout=True,
2826 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002827 r = []
2828 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002829 if line[-1] == '\n':
2830 line = line[:-1]
2831 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002832 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002833 raise GitError('%s rev-list %s: %s' %
2834 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002835 return r
2836
2837 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002838 """Allow arbitrary git commands using pythonic syntax.
2839
2840 This allows you to do things like:
2841 git_obj.rev_parse('HEAD')
2842
2843 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2844 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002845 Any other positional arguments will be passed to the git command, and the
2846 following keyword arguments are supported:
2847 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002848
2849 Args:
2850 name: The name of the git command to call. Any '_' characters will
2851 be replaced with '-'.
2852
2853 Returns:
2854 A callable object that will try to call git with the named command.
2855 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002856 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002857
Dave Borowitz091f8932012-10-23 17:01:04 -07002858 def runner(*args, **kwargs):
2859 cmdv = []
2860 config = kwargs.pop('config', None)
2861 for k in kwargs:
2862 raise TypeError('%s() got an unexpected keyword argument %r'
2863 % (name, k))
2864 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002865 if not git_require((1, 7, 2)):
2866 raise ValueError('cannot set config on command line for %s()'
2867 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302868 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002869 cmdv.append('-c')
2870 cmdv.append('%s=%s' % (k, v))
2871 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002872 cmdv.extend(args)
2873 p = GitCommand(self._project,
2874 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002875 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002876 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002877 capture_stdout=True,
2878 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002879 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002880 raise GitError('%s %s: %s' %
2881 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002882 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302883 try:
Conley Owensedd01512013-09-26 12:59:58 -07002884 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302885 except AttributeError:
2886 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002887 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2888 return r[:-1]
2889 return r
2890 return runner
2891
2892
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002893class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002894
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002895 def __str__(self):
2896 return 'prior sync failed; rebase still in progress'
2897
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002898
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002899class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002900
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002901 def __str__(self):
2902 return 'contains uncommitted changes'
2903
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002904
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002905class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002906
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002907 def __init__(self, project, text):
2908 self.project = project
2909 self.text = text
2910
2911 def Print(self, syncbuf):
2912 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2913 syncbuf.out.nl()
2914
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002915
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002916class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002917
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002918 def __init__(self, project, why):
2919 self.project = project
2920 self.why = why
2921
2922 def Print(self, syncbuf):
2923 syncbuf.out.fail('error: %s/: %s',
2924 self.project.relpath,
2925 str(self.why))
2926 syncbuf.out.nl()
2927
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002928
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002929class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002930
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002931 def __init__(self, project, action):
2932 self.project = project
2933 self.action = action
2934
2935 def Run(self, syncbuf):
2936 out = syncbuf.out
2937 out.project('project %s/', self.project.relpath)
2938 out.nl()
2939 try:
2940 self.action()
2941 out.nl()
2942 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002943 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002944 out.nl()
2945 return False
2946
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002947
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002948class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002949
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002950 def __init__(self, config):
2951 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002952 self.project = self.printer('header', attr='bold')
2953 self.info = self.printer('info')
2954 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002955
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002956
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002957class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002958
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002959 def __init__(self, config, detach_head=False):
2960 self._messages = []
2961 self._failures = []
2962 self._later_queue1 = []
2963 self._later_queue2 = []
2964
2965 self.out = _SyncColoring(config)
2966 self.out.redirect(sys.stderr)
2967
2968 self.detach_head = detach_head
2969 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002970 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002971
2972 def info(self, project, fmt, *args):
2973 self._messages.append(_InfoMessage(project, fmt % args))
2974
2975 def fail(self, project, err=None):
2976 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002977 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002978
2979 def later1(self, project, what):
2980 self._later_queue1.append(_Later(project, what))
2981
2982 def later2(self, project, what):
2983 self._later_queue2.append(_Later(project, what))
2984
2985 def Finish(self):
2986 self._PrintMessages()
2987 self._RunLater()
2988 self._PrintMessages()
2989 return self.clean
2990
David Rileye0684ad2017-04-05 00:02:59 -07002991 def Recently(self):
2992 recent_clean = self.recent_clean
2993 self.recent_clean = True
2994 return recent_clean
2995
2996 def _MarkUnclean(self):
2997 self.clean = False
2998 self.recent_clean = False
2999
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003000 def _RunLater(self):
3001 for q in ['_later_queue1', '_later_queue2']:
3002 if not self._RunQueue(q):
3003 return
3004
3005 def _RunQueue(self, queue):
3006 for m in getattr(self, queue):
3007 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003008 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003009 return False
3010 setattr(self, queue, [])
3011 return True
3012
3013 def _PrintMessages(self):
3014 for m in self._messages:
3015 m.Print(self)
3016 for m in self._failures:
3017 m.Print(self)
3018
3019 self._messages = []
3020 self._failures = []
3021
3022
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003023class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003024
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003025 """A special project housed under .repo.
3026 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003027
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003028 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003029 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003030 manifest=manifest,
3031 name=name,
3032 gitdir=gitdir,
3033 objdir=gitdir,
3034 worktree=worktree,
3035 remote=RemoteSpec('origin'),
3036 relpath='.repo/%s' % name,
3037 revisionExpr='refs/heads/master',
3038 revisionId=None,
3039 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003040
3041 def PreSync(self):
3042 if self.Exists:
3043 cb = self.CurrentBranch
3044 if cb:
3045 base = self.GetBranch(cb).merge
3046 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003047 self.revisionExpr = base
3048 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003049
Martin Kelly224a31a2017-07-10 14:46:25 -07003050 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003051 """ Prepare MetaProject for manifest branch switch
3052 """
3053
3054 # detach and delete manifest branch, allowing a new
3055 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003056 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003057 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003058 syncbuf.Finish()
3059
3060 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003061 ['update-ref', '-d', 'refs/heads/default'],
3062 capture_stdout=True,
3063 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003065 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003066 def LastFetch(self):
3067 try:
3068 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3069 return os.path.getmtime(fh)
3070 except OSError:
3071 return 0
3072
3073 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003074 def HasChanges(self):
3075 """Has the remote received new commits not yet checked out?
3076 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003077 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003078 return False
3079
David Pursehouse8a68ff92012-09-24 12:15:13 +09003080 all_refs = self.bare_ref.all
3081 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003082 head = self.work_git.GetHead()
3083 if head.startswith(R_HEADS):
3084 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003085 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003086 except KeyError:
3087 head = None
3088
3089 if revid == head:
3090 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003091 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003092 return True
3093 return False