blob: 0d68a3ccbbf898e4291194af7622377b35363530 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070034 ID_RE, RefSpec
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070036from error import CacheApplyError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080037from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080038from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070039from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Shawn O. Pearced237b692009-04-17 18:49:50 -070041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
David Pursehouse59bbb582013-05-17 10:49:33 +090043from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040044if is_python3():
45 import urllib.parse
46else:
47 import imp
48 import urlparse
49 urllib = imp.new_module('urllib')
50 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090051 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053052 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090053 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070055
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056def _lwrite(path, content):
57 lock = '%s.lock' % path
58
Chirayu Desai303a82f2014-08-19 22:57:17 +053059 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 try:
61 fd.write(content)
62 finally:
63 fd.close()
64
65 try:
66 os.rename(lock, path)
67 except OSError:
68 os.remove(lock)
69 raise
70
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
Shawn O. Pearce48244782009-04-16 08:25:57 -070072def _error(fmt, *args):
73 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070074 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070075
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
David Pursehousef33929d2015-08-24 14:39:14 +090077def _warn(fmt, *args):
78 msg = fmt % args
79 print('warn: %s' % msg, file=sys.stderr)
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082def not_rev(r):
83 return '^' + r
84
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070085
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080086def sq(r):
87 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
106 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
107 d = os.path.join(d, 'hooks')
108 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
138
139 def __init__(self, project, branch, base):
140 self.project = project
141 self.branch = branch
142 self.base = base
143
144 @property
145 def name(self):
146 return self.branch.name
147
148 @property
149 def commits(self):
150 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700151 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700164 for commit in self.project.bare_git.rev_list(not_rev(self.base),
165 R_HEADS + self.name,
166 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 r[commit[0:8]] = commit
168 return r
169
170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 return self.project.bare_git.log('--pretty=format:%cd',
173 '-n', '1',
174 R_HEADS + self.name,
175 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700177 def UploadForReview(self, people,
178 auto_topic=False,
179 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200180 private=False,
181 wip=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700182 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800183 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700184 people,
Brian Harring435370c2012-07-28 15:37:04 -0700185 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400186 draft=draft,
Changcheng Xiao7da6f862017-08-02 16:55:03 +0200187 private=private,
188 wip=wip,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400189 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700191 def GetPublishedRefs(self):
192 refs = {}
193 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700194 self.branch.remote.SshReviewUrl(self.project.UserEmail),
195 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700196 for line in output.split('\n'):
197 try:
198 (sha, ref) = line.split()
199 refs[sha] = ref
200 except ValueError:
201 pass
202
203 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700205
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 def __init__(self, config):
209 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100210 self.project = self.printer('header', attr='bold')
211 self.branch = self.printer('header', attr='bold')
212 self.nobranch = self.printer('nobranch', fg='red')
213 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
Anthony King7bdac712014-07-16 12:56:40 +0100215 self.added = self.printer('added', fg='green')
216 self.changed = self.printer('changed', fg='red')
217 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218
219
220class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 def __init__(self, config):
223 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100224 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700226
Anthony King7bdac712014-07-16 12:56:40 +0100227class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228
James W. Mills24c13082012-04-12 15:04:13 -0500229 def __init__(self, name, value, keep):
230 self.name = name
231 self.value = value
232 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
Anthony King7bdac712014-07-16 12:56:40 +0100235class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800237 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238 self.src = src
239 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800240 self.abs_src = abssrc
241 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242
243 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800244 src = self.abs_src
245 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246 # copy file if it does not exist or is out of date
247 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
248 try:
249 # remove existing file first, since it might be read-only
250 if os.path.exists(dest):
251 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400252 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200253 dest_dir = os.path.dirname(dest)
254 if not os.path.isdir(dest_dir):
255 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 shutil.copy(src, dest)
257 # make the file read-only
258 mode = os.stat(dest)[stat.ST_MODE]
259 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
260 os.chmod(dest, mode)
261 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700262 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Anthony King7bdac712014-07-16 12:56:40 +0100265class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Wink Saville4c426ef2015-06-03 08:05:17 -0700267 def __init__(self, git_worktree, src, dest, relsrc, absdest):
268 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500269 self.src = src
270 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700271 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 self.abs_dest = absdest
273
Wink Saville4c426ef2015-06-03 08:05:17 -0700274 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500275 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 try:
278 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800279 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700280 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500283 if not os.path.isdir(dest_dir):
284 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700285 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500286 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700287 _error('Cannot link file %s to %s', relSrc, absDest)
288
289 def _Link(self):
290 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
291 on the src linking all of the files in the source in to the destination
292 directory.
293 """
294 # We use the absSrc to handle the situation where the current directory
295 # is not the root of the repo
296 absSrc = os.path.join(self.git_worktree, self.src)
297 if os.path.exists(absSrc):
298 # Entity exists so just a simple one to one link operation
299 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
300 else:
301 # Entity doesn't exist assume there is a wild card
302 absDestDir = self.abs_dest
303 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
304 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700305 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700306 else:
307 absSrcFiles = glob.glob(absSrc)
308 for absSrcFile in absSrcFiles:
309 # Create a releative path from source dir to destination dir
310 absSrcDir = os.path.dirname(absSrcFile)
311 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
312
313 # Get the source file name
314 srcFile = os.path.basename(absSrcFile)
315
316 # Now form the final full paths to srcFile. They will be
317 # absolute for the desintaiton and relative for the srouce.
318 absDest = os.path.join(absDestDir, srcFile)
319 relSrc = os.path.join(relSrcDir, srcFile)
320 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500321
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700322
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700323class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700325 def __init__(self,
326 name,
Anthony King7bdac712014-07-16 12:56:40 +0100327 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700328 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100329 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700330 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700331 orig_name=None,
332 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700333 self.name = name
334 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700335 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700336 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100337 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700338 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700339 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700341
Doug Anderson37282b42011-03-04 11:54:18 -0800342class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700343
Doug Anderson37282b42011-03-04 11:54:18 -0800344 """A RepoHook contains information about a script to run as a hook.
345
346 Hooks are used to run a python script before running an upload (for instance,
347 to run presubmit checks). Eventually, we may have hooks for other actions.
348
349 This shouldn't be confused with files in the 'repo/hooks' directory. Those
350 files are copied into each '.git/hooks' folder for each project. Repo-level
351 hooks are associated instead with repo actions.
352
353 Hooks are always python. When a hook is run, we will load the hook into the
354 interpreter and execute its main() function.
355 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700356
Doug Anderson37282b42011-03-04 11:54:18 -0800357 def __init__(self,
358 hook_type,
359 hooks_project,
360 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400361 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800362 abort_if_user_denies=False):
363 """RepoHook constructor.
364
365 Params:
366 hook_type: A string representing the type of hook. This is also used
367 to figure out the name of the file containing the hook. For
368 example: 'pre-upload'.
369 hooks_project: The project containing the repo hooks. If you have a
370 manifest, this is manifest.repo_hooks_project. OK if this is None,
371 which will make the hook a no-op.
372 topdir: Repo's top directory (the one containing the .repo directory).
373 Scripts will run with CWD as this directory. If you have a manifest,
374 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400375 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800376 abort_if_user_denies: If True, we'll throw a HookError() if the user
377 doesn't allow us to run the hook.
378 """
379 self._hook_type = hook_type
380 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400381 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800382 self._topdir = topdir
383 self._abort_if_user_denies = abort_if_user_denies
384
385 # Store the full path to the script for convenience.
386 if self._hooks_project:
387 self._script_fullpath = os.path.join(self._hooks_project.worktree,
388 self._hook_type + '.py')
389 else:
390 self._script_fullpath = None
391
392 def _GetHash(self):
393 """Return a hash of the contents of the hooks directory.
394
395 We'll just use git to do this. This hash has the property that if anything
396 changes in the directory we will return a different has.
397
398 SECURITY CONSIDERATION:
399 This hash only represents the contents of files in the hook directory, not
400 any other files imported or called by hooks. Changes to imported files
401 can change the script behavior without affecting the hash.
402
403 Returns:
404 A string representing the hash. This will always be ASCII so that it can
405 be printed to the user easily.
406 """
407 assert self._hooks_project, "Must have hooks to calculate their hash."
408
409 # We will use the work_git object rather than just calling GetRevisionId().
410 # That gives us a hash of the latest checked in version of the files that
411 # the user will actually be executing. Specifically, GetRevisionId()
412 # doesn't appear to change even if a user checks out a different version
413 # of the hooks repo (via git checkout) nor if a user commits their own revs.
414 #
415 # NOTE: Local (non-committed) changes will not be factored into this hash.
416 # I think this is OK, since we're really only worried about warning the user
417 # about upstream changes.
418 return self._hooks_project.work_git.rev_parse('HEAD')
419
420 def _GetMustVerb(self):
421 """Return 'must' if the hook is required; 'should' if not."""
422 if self._abort_if_user_denies:
423 return 'must'
424 else:
425 return 'should'
426
427 def _CheckForHookApproval(self):
428 """Check to see whether this hook has been approved.
429
Mike Frysinger40252c22016-08-15 21:23:44 -0400430 We'll accept approval of manifest URLs if they're using secure transports.
431 This way the user can say they trust the manifest hoster. For insecure
432 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800433
434 Note that we ask permission for each individual hook even though we use
435 the hash of all hooks when detecting changes. We'd like the user to be
436 able to approve / deny each hook individually. We only use the hash of all
437 hooks because there is no other easy way to detect changes to local imports.
438
439 Returns:
440 True if this hook is approved to run; False otherwise.
441
442 Raises:
443 HookError: Raised if the user doesn't approve and abort_if_user_denies
444 was passed to the consturctor.
445 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400446 if self._ManifestUrlHasSecureScheme():
447 return self._CheckForHookApprovalManifest()
448 else:
449 return self._CheckForHookApprovalHash()
450
451 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
452 changed_prompt):
453 """Check for approval for a particular attribute and hook.
454
455 Args:
456 subkey: The git config key under [repo.hooks.<hook_type>] to store the
457 last approved string.
458 new_val: The new value to compare against the last approved one.
459 main_prompt: Message to display to the user to ask for approval.
460 changed_prompt: Message explaining why we're re-asking for approval.
461
462 Returns:
463 True if this hook is approved to run; False otherwise.
464
465 Raises:
466 HookError: Raised if the user doesn't approve and abort_if_user_denies
467 was passed to the consturctor.
468 """
Doug Anderson37282b42011-03-04 11:54:18 -0800469 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800471
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 # Get the last value that the user approved for this hook; may be None.
473 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800474
Mike Frysinger40252c22016-08-15 21:23:44 -0400475 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800476 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800478 # Approval matched. We're done.
479 return True
480 else:
481 # Give the user a reason why we're prompting, since they last told
482 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800484 else:
485 prompt = ''
486
487 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
488 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400489 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530490 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900491 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800492
493 # User is doing a one-time approval.
494 if response in ('y', 'yes'):
495 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400496 elif response == 'always':
497 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800498 return True
499
500 # For anything else, we'll assume no approval.
501 if self._abort_if_user_denies:
502 raise HookError('You must allow the %s hook or use --no-verify.' %
503 self._hook_type)
504
505 return False
506
Mike Frysinger40252c22016-08-15 21:23:44 -0400507 def _ManifestUrlHasSecureScheme(self):
508 """Check if the URI for the manifest is a secure transport."""
509 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
510 parse_results = urllib.parse.urlparse(self._manifest_url)
511 return parse_results.scheme in secure_schemes
512
513 def _CheckForHookApprovalManifest(self):
514 """Check whether the user has approved this manifest host.
515
516 Returns:
517 True if this hook is approved to run; False otherwise.
518 """
519 return self._CheckForHookApprovalHelper(
520 'approvedmanifest',
521 self._manifest_url,
522 'Run hook scripts from %s' % (self._manifest_url,),
523 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
524
525 def _CheckForHookApprovalHash(self):
526 """Check whether the user has approved the hooks repo.
527
528 Returns:
529 True if this hook is approved to run; False otherwise.
530 """
531 prompt = ('Repo %s run the script:\n'
532 ' %s\n'
533 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700534 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400535 return self._CheckForHookApprovalHelper(
536 'approvedhash',
537 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700538 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400539 'Scripts have changed since %s was allowed.' % (self._hook_type,))
540
Doug Anderson37282b42011-03-04 11:54:18 -0800541 def _ExecuteHook(self, **kwargs):
542 """Actually execute the given hook.
543
544 This will run the hook's 'main' function in our python interpreter.
545
546 Args:
547 kwargs: Keyword arguments to pass to the hook. These are often specific
548 to the hook type. For instance, pre-upload hooks will contain
549 a project_list.
550 """
551 # Keep sys.path and CWD stashed away so that we can always restore them
552 # upon function exit.
553 orig_path = os.getcwd()
554 orig_syspath = sys.path
555
556 try:
557 # Always run hooks with CWD as topdir.
558 os.chdir(self._topdir)
559
560 # Put the hook dir as the first item of sys.path so hooks can do
561 # relative imports. We want to replace the repo dir as [0] so
562 # hooks can't import repo files.
563 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
564
565 # Exec, storing global context in the context dict. We catch exceptions
566 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500567 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800568 try:
Anthony King70f68902014-05-05 21:15:34 +0100569 exec(compile(open(self._script_fullpath).read(),
570 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800571 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700572 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
573 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800574
575 # Running the script should have defined a main() function.
576 if 'main' not in context:
577 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
578
Doug Anderson37282b42011-03-04 11:54:18 -0800579 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
580 # We don't actually want hooks to define their main with this argument--
581 # it's there to remind them that their hook should always take **kwargs.
582 # For instance, a pre-upload hook should be defined like:
583 # def main(project_list, **kwargs):
584 #
585 # This allows us to later expand the API without breaking old hooks.
586 kwargs = kwargs.copy()
587 kwargs['hook_should_take_kwargs'] = True
588
589 # Call the main function in the hook. If the hook should cause the
590 # build to fail, it will raise an Exception. We'll catch that convert
591 # to a HookError w/ just the failing traceback.
592 try:
593 context['main'](**kwargs)
594 except Exception:
595 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700596 'above.' % (traceback.format_exc(),
597 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800598 finally:
599 # Restore sys.path and CWD.
600 sys.path = orig_syspath
601 os.chdir(orig_path)
602
603 def Run(self, user_allows_all_hooks, **kwargs):
604 """Run the hook.
605
606 If the hook doesn't exist (because there is no hooks project or because
607 this particular hook is not enabled), this is a no-op.
608
609 Args:
610 user_allows_all_hooks: If True, we will never prompt about running the
611 hook--we'll just assume it's OK to run it.
612 kwargs: Keyword arguments to pass to the hook. These are often specific
613 to the hook type. For instance, pre-upload hooks will contain
614 a project_list.
615
616 Raises:
617 HookError: If there was a problem finding the hook or the user declined
618 to run a required hook (from _CheckForHookApproval).
619 """
620 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700621 if ((not self._hooks_project) or (self._hook_type not in
622 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800623 return
624
625 # Bail with a nice error if we can't find the hook.
626 if not os.path.isfile(self._script_fullpath):
627 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
628
629 # Make sure the user is OK with running the hook.
630 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
631 return
632
633 # Run the hook with the same version of python we're using.
634 self._ExecuteHook(**kwargs)
635
636
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600638 # These objects can be shared between several working trees.
639 shareable_files = ['description', 'info']
640 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
641 # These objects can only be used by a single working tree.
642 working_tree_files = ['config', 'packed-refs', 'shallow']
643 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 def __init__(self,
646 manifest,
647 name,
648 remote,
649 gitdir,
David James8d201162013-10-11 17:03:19 -0700650 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651 worktree,
652 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700653 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800654 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100655 rebase=True,
656 groups=None,
657 sync_c=False,
658 sync_s=False,
659 clone_depth=None,
660 upstream=None,
661 parent=None,
662 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900663 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700664 optimized_fetch=False,
665 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800666 """Init a Project object.
667
668 Args:
669 manifest: The XmlManifest object.
670 name: The `name` attribute of manifest.xml's project element.
671 remote: RemoteSpec object specifying its remote's properties.
672 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700673 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800674 worktree: Absolute path of git working tree.
675 relpath: Relative path of git working tree to repo's top directory.
676 revisionExpr: The `revision` attribute of manifest.xml's project element.
677 revisionId: git commit id for checking out.
678 rebase: The `rebase` attribute of manifest.xml's project element.
679 groups: The `groups` attribute of manifest.xml's project element.
680 sync_c: The `sync-c` attribute of manifest.xml's project element.
681 sync_s: The `sync-s` attribute of manifest.xml's project element.
682 upstream: The `upstream` attribute of manifest.xml's project element.
683 parent: The parent Project object.
684 is_derived: False if the project was explicitly defined in the manifest;
685 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400686 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900687 optimized_fetch: If True, when a project is set to a sha1 revision, only
688 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700689 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800690 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 self.manifest = manifest
692 self.name = name
693 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800694 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700695 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800696 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700697 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800698 else:
699 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700701 self.revisionExpr = revisionExpr
702
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700703 if revisionId is None \
704 and revisionExpr \
705 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700706 self.revisionId = revisionExpr
707 else:
708 self.revisionId = revisionId
709
Mike Pontillod3153822012-02-28 11:53:24 -0800710 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700711 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700712 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800713 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900714 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700715 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800716 self.parent = parent
717 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900718 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800719 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800720
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500723 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500724 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700725 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
726 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800728 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700729 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800730 else:
731 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700732 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700733 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700734 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400735 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700736 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
Doug Anderson37282b42011-03-04 11:54:18 -0800738 # This will be filled in if a project is later identified to be the
739 # project containing repo hooks.
740 self.enabled_repo_hooks = []
741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800743 def Derived(self):
744 return self.is_derived
745
746 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600748 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749
750 @property
751 def CurrentBranch(self):
752 """Obtain the name of the currently checked out branch.
753 The branch name omits the 'refs/heads/' prefix.
754 None is returned if the project is on a detached HEAD.
755 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700756 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 if b.startswith(R_HEADS):
758 return b[len(R_HEADS):]
759 return None
760
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700761 def IsRebaseInProgress(self):
762 w = self.worktree
763 g = os.path.join(w, '.git')
764 return os.path.exists(os.path.join(g, 'rebase-apply')) \
765 or os.path.exists(os.path.join(g, 'rebase-merge')) \
766 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 def IsDirty(self, consider_untracked=True):
769 """Is the working directory modified in some way?
770 """
771 self.work_git.update_index('-q',
772 '--unmerged',
773 '--ignore-missing',
774 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900775 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 return True
777 if self.work_git.DiffZ('diff-files'):
778 return True
779 if consider_untracked and self.work_git.LsOthers():
780 return True
781 return False
782
783 _userident_name = None
784 _userident_email = None
785
786 @property
787 def UserName(self):
788 """Obtain the user's personal name.
789 """
790 if self._userident_name is None:
791 self._LoadUserIdentity()
792 return self._userident_name
793
794 @property
795 def UserEmail(self):
796 """Obtain the user's email address. This is very likely
797 to be their Gerrit login.
798 """
799 if self._userident_email is None:
800 self._LoadUserIdentity()
801 return self._userident_email
802
803 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900804 u = self.bare_git.var('GIT_COMMITTER_IDENT')
805 m = re.compile("^(.*) <([^>]*)> ").match(u)
806 if m:
807 self._userident_name = m.group(1)
808 self._userident_email = m.group(2)
809 else:
810 self._userident_name = ''
811 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
813 def GetRemote(self, name):
814 """Get the configuration for a single remote.
815 """
816 return self.config.GetRemote(name)
817
818 def GetBranch(self, name):
819 """Get the configuration for a single branch.
820 """
821 return self.config.GetBranch(name)
822
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700823 def GetBranches(self):
824 """Get all existing local branches.
825 """
826 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700828 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700829
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530830 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831 if name.startswith(R_HEADS):
832 name = name[len(R_HEADS):]
833 b = self.GetBranch(name)
834 b.current = name == current
835 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900836 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700837 heads[name] = b
838
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530839 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700840 if name.startswith(R_PUB):
841 name = name[len(R_PUB):]
842 b = heads.get(name)
843 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700845
846 return heads
847
Colin Cross5acde752012-03-28 20:15:45 -0700848 def MatchesGroups(self, manifest_groups):
849 """Returns true if the manifest groups specified at init should cause
850 this project to be synced.
851 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700852 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700853
854 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700855 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700856 manifest_groups: "-group1,group2"
857 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500858
859 The special manifest group "default" will match any project that
860 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700861 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500862 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700863 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700864 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500865 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700866
Conley Owens971de8e2012-04-16 10:36:08 -0700867 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700868 for group in expanded_manifest_groups:
869 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700870 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700871 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700872 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700873
Conley Owens971de8e2012-04-16 10:36:08 -0700874 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700876# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700877 def UncommitedFiles(self, get_all=True):
878 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700880 Args:
881 get_all: a boolean, if True - get information about all different
882 uncommitted files. If False - return as soon as any kind of
883 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500884 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700885 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500886 self.work_git.update_index('-q',
887 '--unmerged',
888 '--ignore-missing',
889 '--refresh')
890 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700891 details.append("rebase in progress")
892 if not get_all:
893 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500894
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700895 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
896 if changes:
897 details.extend(changes)
898 if not get_all:
899 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500900
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700901 changes = self.work_git.DiffZ('diff-files').keys()
902 if changes:
903 details.extend(changes)
904 if not get_all:
905 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500906
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700907 changes = self.work_git.LsOthers()
908 if changes:
909 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500910
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700911 return details
912
913 def HasChanges(self):
914 """Returns true if there are uncommitted changes.
915 """
916 if self.UncommitedFiles(get_all=False):
917 return True
918 else:
919 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500920
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600921 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200923
924 Args:
925 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600926 quiet: If True then only print the project name. Do not print
927 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 """
929 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700930 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200931 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700932 print(file=output_redir)
933 print('project %s/' % self.relpath, file=output_redir)
934 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935 return
936
937 self.work_git.update_index('-q',
938 '--unmerged',
939 '--ignore-missing',
940 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700941 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
943 df = self.work_git.DiffZ('diff-files')
944 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100945 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700946 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947
948 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700949 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200950 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700951 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600953 if quiet:
954 out.nl()
955 return 'DIRTY'
956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 branch = self.CurrentBranch
958 if branch is None:
959 out.nobranch('(*** NO BRANCH ***)')
960 else:
961 out.branch('branch %s', branch)
962 out.nl()
963
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700964 if rb:
965 out.important('prior sync failed; rebase still in progress')
966 out.nl()
967
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 paths = list()
969 paths.extend(di.keys())
970 paths.extend(df.keys())
971 paths.extend(do)
972
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530973 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900974 try:
975 i = di[p]
976 except KeyError:
977 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900979 try:
980 f = df[p]
981 except KeyError:
982 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200983
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900984 if i:
985 i_status = i.status.upper()
986 else:
987 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900989 if f:
990 f_status = f.status.lower()
991 else:
992 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
994 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800995 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700996 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 else:
998 line = ' %s%s\t%s' % (i_status, f_status, p)
999
1000 if i and not f:
1001 out.added('%s', line)
1002 elif (i and f) or (not i and f):
1003 out.changed('%s', line)
1004 elif not i and not f:
1005 out.untracked('%s', line)
1006 else:
1007 out.write('%s', line)
1008 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001009
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001010 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011
pelyad67872d2012-03-28 14:49:58 +03001012 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 """Prints the status of the repository to stdout.
1014 """
1015 out = DiffColoring(self.config)
1016 cmd = ['diff']
1017 if out.is_on:
1018 cmd.append('--color')
1019 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001020 if absolute_paths:
1021 cmd.append('--src-prefix=a/%s/' % self.relpath)
1022 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 cmd.append('--')
1024 p = GitCommand(self,
1025 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001026 capture_stdout=True,
1027 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 has_diff = False
1029 for line in p.process.stdout:
1030 if not has_diff:
1031 out.nl()
1032 out.project('project %s/' % self.relpath)
1033 out.nl()
1034 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001035 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 p.Wait()
1037
1038
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001039# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040
David Pursehouse8a68ff92012-09-24 12:15:13 +09001041 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 """Was the branch published (uploaded) for code review?
1043 If so, returns the SHA-1 hash of the last published
1044 state for the branch.
1045 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001046 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001047 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001048 try:
1049 return self.bare_git.rev_parse(key)
1050 except GitError:
1051 return None
1052 else:
1053 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001054 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001055 except KeyError:
1056 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
David Pursehouse8a68ff92012-09-24 12:15:13 +09001058 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 """Prunes any stale published refs.
1060 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001061 if all_refs is None:
1062 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 heads = set()
1064 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301065 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 if name.startswith(R_HEADS):
1067 heads.add(name)
1068 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001069 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301071 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 n = name[len(R_PUB):]
1073 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001074 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001076 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 """List any branches which can be uploaded for review.
1078 """
1079 heads = {}
1080 pubed = {}
1081
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301082 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001084 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001086 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087
1088 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301089 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001090 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001092 if selected_branch and branch != selected_branch:
1093 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001095 rb = self.GetUploadableBranch(branch)
1096 if rb:
1097 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098 return ready
1099
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001100 def GetUploadableBranch(self, branch_name):
1101 """Get a single uploadable branch, or None.
1102 """
1103 branch = self.GetBranch(branch_name)
1104 base = branch.LocalMerge
1105 if branch.LocalMerge:
1106 rb = ReviewableBranch(self, branch, base)
1107 if rb.commits:
1108 return rb
1109 return None
1110
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001111 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001112 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001113 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001114 draft=False,
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001115 private=False,
1116 wip=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001117 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 """Uploads the named branch for code review.
1119 """
1120 if branch is None:
1121 branch = self.CurrentBranch
1122 if branch is None:
1123 raise GitError('not currently on a branch')
1124
1125 branch = self.GetBranch(branch)
1126 if not branch.LocalMerge:
1127 raise GitError('branch %s does not track a remote' % branch.name)
1128 if not branch.remote.review:
1129 raise GitError('remote %s has no review url' % branch.remote.name)
1130
Bryan Jacobsf609f912013-05-06 13:36:24 -04001131 if dest_branch is None:
1132 dest_branch = self.dest_branch
1133 if dest_branch is None:
1134 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 if not dest_branch.startswith(R_HEADS):
1136 dest_branch = R_HEADS + dest_branch
1137
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001138 if not branch.remote.projectname:
1139 branch.remote.projectname = self.name
1140 branch.remote.Save()
1141
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001142 url = branch.remote.ReviewUrl(self.UserEmail)
1143 if url is None:
1144 raise UploadError('review not configured')
1145 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001146
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001147 if url.startswith('ssh://'):
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001148 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001149
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001150 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001151
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001152 if dest_branch.startswith(R_HEADS):
1153 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001154
1155 upload_type = 'for'
1156 if draft:
1157 upload_type = 'drafts'
1158
1159 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1160 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001161 if auto_topic:
1162 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao7da6f862017-08-02 16:55:03 +02001163
Jonathan Nieder4ad4c462018-11-05 13:21:52 -08001164 opts = ['r=%s' % p for p in people[0]]
1165 opts += ['cc=%s' % p for p in people[1]]
1166 if private:
1167 opts += ['private']
1168 if wip:
1169 opts += ['wip']
1170 if opts:
1171 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001172 cmd.append(ref_spec)
1173
Anthony King7bdac712014-07-16 12:56:40 +01001174 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001175 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
1177 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1178 self.bare_git.UpdateRef(R_PUB + branch.name,
1179 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001180 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
1182
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001183# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184
Julien Campergue335f5ef2013-10-16 11:02:35 +02001185 def _ExtractArchive(self, tarpath, path=None):
1186 """Extract the given tar on its current location
1187
1188 Args:
1189 - tarpath: The path to the actual tar file
1190
1191 """
1192 try:
1193 with tarfile.open(tarpath, 'r') as tar:
1194 tar.extractall(path=path)
1195 return True
1196 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001197 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001198 return False
1199
Ningning Xiac2fbc782016-08-22 14:24:39 -07001200 def CachePopulate(self, cache_dir, url):
1201 """Populate cache in the cache_dir.
1202
1203 Args:
1204 cache_dir: Directory to cache git files from Google Storage.
1205 url: Git url of current repository.
1206
1207 Raises:
1208 CacheApplyError if it fails to populate the git cache.
1209 """
1210 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1211 '--cache-dir', cache_dir, url]
1212
1213 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1214 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1215 'url: %s' % (cache_dir, url))
1216
1217 def CacheExists(self, cache_dir, url):
1218 """Check the existence of the cache files.
1219
1220 Args:
1221 cache_dir: Directory to cache git files.
1222 url: Git url of current repository.
1223
1224 Raises:
1225 CacheApplyError if the cache files do not exist.
1226 """
1227 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1228
1229 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1230 if exist.Wait() != 0:
1231 raise CacheApplyError('Failed to execute git cache exists cmd. '
1232 'cache_dir: %s url: %s' % (cache_dir, url))
1233
1234 if not exist.stdout or not exist.stdout.strip():
1235 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1236 'url: %s' % (cache_dir, url))
1237 return exist.stdout.strip()
1238
1239 def CacheApply(self, cache_dir):
1240 """Apply git cache files populated from Google Storage buckets.
1241
1242 Args:
1243 cache_dir: Directory to cache git files.
1244
1245 Raises:
1246 CacheApplyError if it fails to apply git caches.
1247 """
1248 remote = self.GetRemote(self.remote.name)
1249
1250 self.CachePopulate(cache_dir, remote.url)
1251
1252 mirror_dir = self.CacheExists(cache_dir, remote.url)
1253
1254 refspec = RefSpec(True, 'refs/heads/*',
1255 'refs/remotes/%s/*' % remote.name)
1256
1257 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1258 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1259 raise CacheApplyError('Failed to fetch refs %s from %s' %
1260 (mirror_dir, str(refspec)))
1261
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001262 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001263 quiet=False,
1264 is_new=None,
1265 current_branch_only=False,
1266 force_sync=False,
1267 clone_bundle=True,
1268 no_tags=False,
1269 archive=False,
1270 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001271 prune=False,
Mike Frysingerde72f6a2018-12-13 03:20:31 -05001272 submodules=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001273 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 """Perform only the network IO portion of the sync process.
1275 Local working directory/branch state is not affected.
1276 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001277 if archive and not isinstance(self, MetaProject):
1278 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001279 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001280 return False
1281
1282 name = self.relpath.replace('\\', '/')
1283 name = name.replace('/', '_')
1284 tarpath = '%s.tar' % name
1285 topdir = self.manifest.topdir
1286
1287 try:
1288 self._FetchArchive(tarpath, cwd=topdir)
1289 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001290 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001291 return False
1292
1293 # From now on, we only need absolute tarpath
1294 tarpath = os.path.join(topdir, tarpath)
1295
1296 if not self._ExtractArchive(tarpath, path=topdir):
1297 return False
1298 try:
1299 os.remove(tarpath)
1300 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001301 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001302 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001303 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001304 if is_new is None:
1305 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001306 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001307 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001308 else:
1309 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001311
1312 if is_new:
1313 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1314 try:
1315 fd = open(alt, 'rb')
1316 try:
1317 alt_dir = fd.readline().rstrip()
1318 finally:
1319 fd.close()
1320 except IOError:
1321 alt_dir = None
1322 else:
1323 alt_dir = None
1324
Ningning Xiac2fbc782016-08-22 14:24:39 -07001325 applied_cache = False
1326 # If cache_dir is provided, and it's a new repository without
1327 # alternative_dir, bootstrap this project repo with the git
1328 # cache files.
1329 if cache_dir is not None and is_new and alt_dir is None:
1330 try:
1331 self.CacheApply(cache_dir)
1332 applied_cache = True
1333 is_new = False
1334 except CacheApplyError as e:
1335 _error('Could not apply git cache: %s', e)
1336 _error('Please check if you have the right GS credentials.')
1337 _error('Please check if the cache files exist in GS.')
1338
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001339 if clone_bundle \
Ningning Xiac2fbc782016-08-22 14:24:39 -07001340 and not applied_cache \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001341 and alt_dir is None \
1342 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001343 is_new = False
1344
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001345 if not current_branch_only:
1346 if self.sync_c:
1347 current_branch_only = True
1348 elif not self.manifest._loaded:
1349 # Manifest cannot check defaults until it syncs.
1350 current_branch_only = False
1351 elif self.manifest.default.sync_c:
1352 current_branch_only = True
1353
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001354 if self.clone_depth:
1355 depth = self.clone_depth
1356 else:
1357 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1358
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001359 need_to_fetch = not (optimized_fetch and
1360 (ID_RE.match(self.revisionExpr) and
1361 self._CheckForSha1()))
1362 if (need_to_fetch and
1363 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1364 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001365 no_tags=no_tags, prune=prune, depth=depth,
1366 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001367 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001368
1369 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001370 self._InitMRef()
1371 else:
1372 self._InitMirrorHead()
1373 try:
1374 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1375 except OSError:
1376 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001378
1379 def PostRepoUpgrade(self):
1380 self._InitHooks()
1381
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001382 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001383 if self.manifest.isGitcClient:
1384 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001385 for copyfile in self.copyfiles:
1386 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001387 for linkfile in self.linkfiles:
1388 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389
Julien Camperguedd654222014-01-09 16:21:37 +01001390 def GetCommitRevisionId(self):
1391 """Get revisionId of a commit.
1392
1393 Use this method instead of GetRevisionId to get the id of the commit rather
1394 than the id of the current git object (for example, a tag)
1395
1396 """
1397 if not self.revisionExpr.startswith(R_TAGS):
1398 return self.GetRevisionId(self._allrefs)
1399
1400 try:
1401 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1402 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001403 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1404 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001405
David Pursehouse8a68ff92012-09-24 12:15:13 +09001406 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001407 if self.revisionId:
1408 return self.revisionId
1409
1410 rem = self.GetRemote(self.remote.name)
1411 rev = rem.ToLocal(self.revisionExpr)
1412
David Pursehouse8a68ff92012-09-24 12:15:13 +09001413 if all_refs is not None and rev in all_refs:
1414 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001415
1416 try:
1417 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1418 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001419 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1420 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001421
Martin Kellye4e94d22017-03-21 16:05:12 -07001422 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423 """Perform only the local IO portion of the sync process.
1424 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001426 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001427 all_refs = self.bare_ref.all
1428 self.CleanPublishedCache(all_refs)
1429 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001430
David Pursehouse1d947b32012-10-25 12:23:11 +09001431 def _doff():
1432 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001433 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001434
Martin Kellye4e94d22017-03-21 16:05:12 -07001435 def _dosubmodules():
1436 self._SyncSubmodules(quiet=True)
1437
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001438 head = self.work_git.GetHead()
1439 if head.startswith(R_HEADS):
1440 branch = head[len(R_HEADS):]
1441 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001442 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001443 except KeyError:
1444 head = None
1445 else:
1446 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001447
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001448 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001449 # Currently on a detached HEAD. The user is assumed to
1450 # not have any local modifications worth worrying about.
1451 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001452 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001453 syncbuf.fail(self, _PriorSyncFailedError())
1454 return
1455
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001456 if head == revid:
1457 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001458 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001459 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001460 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001461 # The copy/linkfile config may have changed.
1462 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001463 return
1464 else:
1465 lost = self._revlist(not_rev(revid), HEAD)
1466 if lost:
1467 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001468
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001469 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001470 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001471 if submodules:
1472 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001473 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001474 syncbuf.fail(self, e)
1475 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001476 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001477 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001479 if head == revid:
1480 # No changes; don't do anything further.
1481 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001482 # The copy/linkfile config may have changed.
1483 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001484 return
1485
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001488 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001489 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001490 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001492 syncbuf.info(self,
1493 "leaving %s; does not track upstream",
1494 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001496 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001497 if submodules:
1498 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001499 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001500 syncbuf.fail(self, e)
1501 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001502 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001503 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001505 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001506 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001507 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001508 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001509 if not_merged:
1510 if upstream_gain:
1511 # The user has published this branch and some of those
1512 # commits are not yet merged upstream. We do not want
1513 # to rewrite the published commits so we punt.
1514 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001515 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001516 "branch %s is published (but not merged) and is now "
1517 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001518 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001519 elif pub == head:
1520 # All published commits are merged, and thus we are a
1521 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001522 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001523 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001524 if submodules:
1525 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001526 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001528 # Examine the local commits not in the remote. Find the
1529 # last one attributed to this user, if any.
1530 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001531 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001532 last_mine = None
1533 cnt_mine = 0
1534 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301535 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001536 if committer_email == self.UserEmail:
1537 last_mine = commit_id
1538 cnt_mine += 1
1539
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001540 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001541 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001542
1543 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001544 syncbuf.fail(self, _DirtyError())
1545 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001546
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001547 # If the upstream switched on us, warn the user.
1548 #
1549 if branch.merge != self.revisionExpr:
1550 if branch.merge and self.revisionExpr:
1551 syncbuf.info(self,
1552 'manifest switched %s...%s',
1553 branch.merge,
1554 self.revisionExpr)
1555 elif branch.merge:
1556 syncbuf.info(self,
1557 'manifest no longer tracks %s',
1558 branch.merge)
1559
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001560 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001562 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001564 syncbuf.info(self,
1565 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001566 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001568 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001569 if not ID_RE.match(self.revisionExpr):
1570 # in case of manifest sync the revisionExpr might be a SHA1
1571 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001572 if not branch.merge.startswith('refs/'):
1573 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574 branch.Save()
1575
Mike Pontillod3153822012-02-28 11:53:24 -08001576 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001577 def _docopyandlink():
1578 self._CopyAndLinkFiles()
1579
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001580 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001581 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001582 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001583 if submodules:
1584 syncbuf.later2(self, _dosubmodules)
1585 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001586 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001587 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001588 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001589 if submodules:
1590 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001591 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001592 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001593 syncbuf.fail(self, e)
1594 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001595 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001596 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001597 if submodules:
1598 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001600 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001601 # dest should already be an absolute path, but src is project relative
1602 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001603 abssrc = os.path.join(self.worktree, src)
1604 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001606 def AddLinkFile(self, src, dest, absdest):
1607 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001608 # make src relative path to dest
1609 absdestdir = os.path.dirname(absdest)
1610 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001611 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001612
James W. Mills24c13082012-04-12 15:04:13 -05001613 def AddAnnotation(self, name, value, keep):
1614 self.annotations.append(_Annotation(name, value, keep))
1615
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001616 def DownloadPatchSet(self, change_id, patch_id):
1617 """Download a single patch set of a single change to FETCH_HEAD.
1618 """
1619 remote = self.GetRemote(self.remote.name)
1620
1621 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001622 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001623 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001624 if GitCommand(self, cmd, bare=True).Wait() != 0:
1625 return None
1626 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001627 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001628 change_id,
1629 patch_id,
1630 self.bare_git.rev_parse('FETCH_HEAD'))
1631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001632
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001633# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001634
Simran Basib9a1b732015-08-20 12:19:28 -07001635 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001636 """Create a new branch off the manifest's revision.
1637 """
Simran Basib9a1b732015-08-20 12:19:28 -07001638 if not branch_merge:
1639 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001640 head = self.work_git.GetHead()
1641 if head == (R_HEADS + name):
1642 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643
David Pursehouse8a68ff92012-09-24 12:15:13 +09001644 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001645 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001646 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001647 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001648 capture_stdout=True,
1649 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001650
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001651 branch = self.GetBranch(name)
1652 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001653 branch.merge = branch_merge
1654 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1655 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001656 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001657
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001658 if head.startswith(R_HEADS):
1659 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001660 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001661 except KeyError:
1662 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001663 if revid and head and revid == head:
1664 ref = os.path.join(self.gitdir, R_HEADS + name)
1665 try:
1666 os.makedirs(os.path.dirname(ref))
1667 except OSError:
1668 pass
1669 _lwrite(ref, '%s\n' % revid)
1670 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1671 'ref: %s%s\n' % (R_HEADS, name))
1672 branch.Save()
1673 return True
1674
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001675 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001676 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001677 capture_stdout=True,
1678 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001679 branch.Save()
1680 return True
1681 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682
Wink Saville02d79452009-04-10 13:01:24 -07001683 def CheckoutBranch(self, name):
1684 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001685
1686 Args:
1687 name: The name of the branch to checkout.
1688
1689 Returns:
1690 True if the checkout succeeded; False if it didn't; None if the branch
1691 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001692 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001693 rev = R_HEADS + name
1694 head = self.work_git.GetHead()
1695 if head == rev:
1696 # Already on the branch
1697 #
1698 return True
Wink Saville02d79452009-04-10 13:01:24 -07001699
David Pursehouse8a68ff92012-09-24 12:15:13 +09001700 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001701 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001702 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001703 except KeyError:
1704 # Branch does not exist in this project
1705 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001706 return None
Wink Saville02d79452009-04-10 13:01:24 -07001707
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001708 if head.startswith(R_HEADS):
1709 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001710 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001711 except KeyError:
1712 head = None
1713
1714 if head == revid:
1715 # Same revision; just update HEAD to point to the new
1716 # target branch, but otherwise take no other action.
1717 #
1718 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1719 'ref: %s%s\n' % (R_HEADS, name))
1720 return True
1721
1722 return GitCommand(self,
1723 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001724 capture_stdout=True,
1725 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001726
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001727 def AbandonBranch(self, name):
1728 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001729
1730 Args:
1731 name: The name of the branch to abandon.
1732
1733 Returns:
1734 True if the abandon succeeded; False if it didn't; None if the branch
1735 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001736 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001737 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001738 all_refs = self.bare_ref.all
1739 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001740 # Doesn't exist
1741 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001742
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001743 head = self.work_git.GetHead()
1744 if head == rev:
1745 # We can't destroy the branch while we are sitting
1746 # on it. Switch to a detached HEAD.
1747 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001748 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001749
David Pursehouse8a68ff92012-09-24 12:15:13 +09001750 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001751 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001752 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1753 '%s\n' % revid)
1754 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001755 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001756
1757 return GitCommand(self,
1758 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001759 capture_stdout=True,
1760 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001761
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762 def PruneHeads(self):
1763 """Prune any topic branches already merged into upstream.
1764 """
1765 cb = self.CurrentBranch
1766 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001767 left = self._allrefs
1768 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 if name.startswith(R_HEADS):
1770 name = name[len(R_HEADS):]
1771 if cb is None or name != cb:
1772 kill.append(name)
1773
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001774 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001775 if cb is not None \
1776 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001777 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778 self.work_git.DetachHead(HEAD)
1779 kill.append(cb)
1780
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001782 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 try:
1785 self.bare_git.DetachHead(rev)
1786
1787 b = ['branch', '-d']
1788 b.extend(kill)
1789 b = GitCommand(self, b, bare=True,
1790 capture_stdout=True,
1791 capture_stderr=True)
1792 b.Wait()
1793 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001794 if ID_RE.match(old):
1795 self.bare_git.DetachHead(old)
1796 else:
1797 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001798 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001799
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001800 for branch in kill:
1801 if (R_HEADS + branch) not in left:
1802 self.CleanPublishedCache()
1803 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804
1805 if cb and cb not in kill:
1806 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001807 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808
1809 kept = []
1810 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001811 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001812 branch = self.GetBranch(branch)
1813 base = branch.LocalMerge
1814 if not base:
1815 base = rev
1816 kept.append(ReviewableBranch(self, branch, base))
1817 return kept
1818
1819
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001820# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001821
1822 def GetRegisteredSubprojects(self):
1823 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001824
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001825 def rec(subprojects):
1826 if not subprojects:
1827 return
1828 result.extend(subprojects)
1829 for p in subprojects:
1830 rec(p.subprojects)
1831 rec(self.subprojects)
1832 return result
1833
1834 def _GetSubmodules(self):
1835 # Unfortunately we cannot call `git submodule status --recursive` here
1836 # because the working tree might not exist yet, and it cannot be used
1837 # without a working tree in its current implementation.
1838
1839 def get_submodules(gitdir, rev):
1840 # Parse .gitmodules for submodule sub_paths and sub_urls
1841 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1842 if not sub_paths:
1843 return []
1844 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1845 # revision of submodule repository
1846 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1847 submodules = []
1848 for sub_path, sub_url in zip(sub_paths, sub_urls):
1849 try:
1850 sub_rev = sub_revs[sub_path]
1851 except KeyError:
1852 # Ignore non-exist submodules
1853 continue
1854 submodules.append((sub_rev, sub_path, sub_url))
1855 return submodules
1856
1857 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1858 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001859
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001860 def parse_gitmodules(gitdir, rev):
1861 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1862 try:
Anthony King7bdac712014-07-16 12:56:40 +01001863 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1864 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001865 except GitError:
1866 return [], []
1867 if p.Wait() != 0:
1868 return [], []
1869
1870 gitmodules_lines = []
1871 fd, temp_gitmodules_path = tempfile.mkstemp()
1872 try:
1873 os.write(fd, p.stdout)
1874 os.close(fd)
1875 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001876 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1877 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001878 if p.Wait() != 0:
1879 return [], []
1880 gitmodules_lines = p.stdout.split('\n')
1881 except GitError:
1882 return [], []
1883 finally:
1884 os.remove(temp_gitmodules_path)
1885
1886 names = set()
1887 paths = {}
1888 urls = {}
1889 for line in gitmodules_lines:
1890 if not line:
1891 continue
1892 m = re_path.match(line)
1893 if m:
1894 names.add(m.group(1))
1895 paths[m.group(1)] = m.group(2)
1896 continue
1897 m = re_url.match(line)
1898 if m:
1899 names.add(m.group(1))
1900 urls[m.group(1)] = m.group(2)
1901 continue
1902 names = sorted(names)
1903 return ([paths.get(name, '') for name in names],
1904 [urls.get(name, '') for name in names])
1905
1906 def git_ls_tree(gitdir, rev, paths):
1907 cmd = ['ls-tree', rev, '--']
1908 cmd.extend(paths)
1909 try:
Anthony King7bdac712014-07-16 12:56:40 +01001910 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1911 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001912 except GitError:
1913 return []
1914 if p.Wait() != 0:
1915 return []
1916 objects = {}
1917 for line in p.stdout.split('\n'):
1918 if not line.strip():
1919 continue
1920 object_rev, object_path = line.split()[2:4]
1921 objects[object_path] = object_rev
1922 return objects
1923
1924 try:
1925 rev = self.GetRevisionId()
1926 except GitError:
1927 return []
1928 return get_submodules(self.gitdir, rev)
1929
1930 def GetDerivedSubprojects(self):
1931 result = []
1932 if not self.Exists:
1933 # If git repo does not exist yet, querying its submodules will
1934 # mess up its states; so return here.
1935 return result
1936 for rev, path, url in self._GetSubmodules():
1937 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001938 relpath, worktree, gitdir, objdir = \
1939 self.manifest.GetSubprojectPaths(self, name, path)
1940 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001941 if project:
1942 result.extend(project.GetDerivedSubprojects())
1943 continue
David James8d201162013-10-11 17:03:19 -07001944
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001945 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001946 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001947 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001948 review=self.remote.review,
1949 revision=self.remote.revision)
1950 subproject = Project(manifest=self.manifest,
1951 name=name,
1952 remote=remote,
1953 gitdir=gitdir,
1954 objdir=objdir,
1955 worktree=worktree,
1956 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001957 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001958 revisionId=rev,
1959 rebase=self.rebase,
1960 groups=self.groups,
1961 sync_c=self.sync_c,
1962 sync_s=self.sync_s,
1963 parent=self,
1964 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001965 result.append(subproject)
1966 result.extend(subproject.GetDerivedSubprojects())
1967 return result
1968
1969
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001970# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001971 def _CheckForSha1(self):
1972 try:
1973 # if revision (sha or tag) is not present then following function
1974 # throws an error.
1975 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1976 return True
1977 except GitError:
1978 # There is no such persistent revision. We have to fetch it.
1979 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001980
Julien Campergue335f5ef2013-10-16 11:02:35 +02001981 def _FetchArchive(self, tarpath, cwd=None):
1982 cmd = ['archive', '-v', '-o', tarpath]
1983 cmd.append('--remote=%s' % self.remote.url)
1984 cmd.append('--prefix=%s/' % self.relpath)
1985 cmd.append(self.revisionExpr)
1986
1987 command = GitCommand(self, cmd, cwd=cwd,
1988 capture_stdout=True,
1989 capture_stderr=True)
1990
1991 if command.Wait() != 0:
1992 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1993
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001994 def _RemoteFetch(self, name=None,
1995 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001996 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001997 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001998 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001999 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002000 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002001 depth=None,
2002 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002003
2004 is_sha1 = False
2005 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002006 # The depth should not be used when fetching to a mirror because
2007 # it will result in a shallow repository that cannot be cloned or
2008 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002009 # The repo project should also never be synced with partial depth.
2010 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2011 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002012
Shawn Pearce69e04d82014-01-29 12:48:54 -08002013 if depth:
2014 current_branch_only = True
2015
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002016 if ID_RE.match(self.revisionExpr) is not None:
2017 is_sha1 = True
2018
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002019 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002020 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002021 # this is a tag and its sha1 value should never change
2022 tag_name = self.revisionExpr[len(R_TAGS):]
2023
2024 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05002025 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002026 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002027 if is_sha1 and not depth:
2028 # When syncing a specific commit and --depth is not set:
2029 # * if upstream is explicitly specified and is not a sha1, fetch only
2030 # upstream as users expect only upstream to be fetch.
2031 # Note: The commit might not be in upstream in which case the sync
2032 # will fail.
2033 # * otherwise, fetch all branches to make sure we end up with the
2034 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002035 if self.upstream:
2036 current_branch_only = not ID_RE.match(self.upstream)
2037 else:
2038 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002039
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002040 if not name:
2041 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002042
2043 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002044 remote = self.GetRemote(name)
2045 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002046 ssh_proxy = True
2047
Shawn O. Pearce88443382010-10-08 10:02:09 +02002048 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002049 if alt_dir and 'objects' == os.path.basename(alt_dir):
2050 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002051 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2052 remote = self.GetRemote(name)
2053
David Pursehouse8a68ff92012-09-24 12:15:13 +09002054 all_refs = self.bare_ref.all
2055 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056 tmp = set()
2057
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302058 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002059 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002060 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002061 all_refs[r] = ref_id
2062 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002063 continue
2064
David Pursehouse8a68ff92012-09-24 12:15:13 +09002065 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002066 continue
2067
David Pursehouse8a68ff92012-09-24 12:15:13 +09002068 r = 'refs/_alt/%s' % ref_id
2069 all_refs[r] = ref_id
2070 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002071 tmp.add(r)
2072
heping3d7bbc92017-04-12 19:51:47 +08002073 tmp_packed_lines = []
2074 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002075
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302076 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002077 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002078 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002079 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002080 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002081
heping3d7bbc92017-04-12 19:51:47 +08002082 tmp_packed = ''.join(tmp_packed_lines)
2083 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002084 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002085 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002086 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002087
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002088 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002089
Conley Owensf97e8382015-01-21 11:12:46 -08002090 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002091 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002092 else:
2093 # If this repo has shallow objects, then we don't know which refs have
2094 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2095 # do this with projects that don't have shallow objects, since it is less
2096 # efficient.
2097 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2098 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002099
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002100 if quiet:
2101 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002102 if not self.worktree:
2103 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002104 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002105
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002106 # If using depth then we should not get all the tags since they may
2107 # be outside of the depth.
2108 if no_tags or depth:
2109 cmd.append('--no-tags')
2110 else:
2111 cmd.append('--tags')
2112
David Pursehouse74cfd272015-10-14 10:50:15 +09002113 if prune:
2114 cmd.append('--prune')
2115
Martin Kellye4e94d22017-03-21 16:05:12 -07002116 if submodules:
2117 cmd.append('--recurse-submodules=on-demand')
2118
Conley Owens80b87fe2014-05-09 17:13:44 -07002119 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002120 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002121 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002122 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002123 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002124 spec.append('tag')
2125 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002126
David Pursehouse403b64e2015-04-27 10:41:33 +09002127 if not self.manifest.IsMirror:
2128 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002129 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002130 # Shallow checkout of a specific commit, fetch from that commit and not
2131 # the heads only as the commit might be deeper in the history.
2132 spec.append(branch)
2133 else:
2134 if is_sha1:
2135 branch = self.upstream
2136 if branch is not None and branch.strip():
2137 if not branch.startswith('refs/'):
2138 branch = R_HEADS + branch
2139 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002140 cmd.extend(spec)
2141
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002142 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002143 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002144 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002145 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002146 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002147 ok = True
2148 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002149 # If needed, run the 'git remote prune' the first time through the loop
2150 elif (not _i and
2151 "error:" in gitcmd.stderr and
2152 "git remote prune" in gitcmd.stderr):
2153 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002154 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002155 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002156 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002157 break
2158 continue
Brian Harring14a66742012-09-28 20:21:57 -07002159 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002160 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2161 # in sha1 mode, we just tried sync'ing from the upstream field; it
2162 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002163 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002164 elif ret < 0:
2165 # Git died with a signal, exit immediately
2166 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002167 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002168
2169 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002170 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002171 if old_packed != '':
2172 _lwrite(packed_refs, old_packed)
2173 else:
2174 os.remove(packed_refs)
2175 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002176
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002177 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002178 # We just synced the upstream given branch; verify we
2179 # got what we wanted, else trigger a second run of all
2180 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002181 if not self._CheckForSha1():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002182 if current_branch_only and depth:
2183 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002184 return self._RemoteFetch(name=name,
2185 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002186 initial=False, quiet=quiet, alt_dir=alt_dir,
2187 depth=None)
2188 else:
2189 # Avoid infinite recursion: sync all branches with depth set to None
2190 return self._RemoteFetch(name=name, current_branch_only=False,
2191 initial=False, quiet=quiet, alt_dir=alt_dir,
2192 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002193
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002194 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002195
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002196 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002197 if initial and \
2198 (self.manifest.manifestProject.config.GetString('repo.depth') or
2199 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002200 return False
2201
2202 remote = self.GetRemote(self.remote.name)
2203 bundle_url = remote.url + '/clone.bundle'
2204 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002205 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2206 'persistent-http',
2207 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002208 return False
2209
2210 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2211 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2212
2213 exist_dst = os.path.exists(bundle_dst)
2214 exist_tmp = os.path.exists(bundle_tmp)
2215
2216 if not initial and not exist_dst and not exist_tmp:
2217 return False
2218
2219 if not exist_dst:
2220 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2221 if not exist_dst:
2222 return False
2223
2224 cmd = ['fetch']
2225 if quiet:
2226 cmd.append('--quiet')
2227 if not self.worktree:
2228 cmd.append('--update-head-ok')
2229 cmd.append(bundle_dst)
2230 for f in remote.fetch:
2231 cmd.append(str(f))
2232 cmd.append('refs/tags/*:refs/tags/*')
2233
2234 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002235 if os.path.exists(bundle_dst):
2236 os.remove(bundle_dst)
2237 if os.path.exists(bundle_tmp):
2238 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002239 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002240
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002241 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002242 if os.path.exists(dstPath):
2243 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002244
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002245 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002246 if quiet:
2247 cmd += ['--silent']
2248 if os.path.exists(tmpPath):
2249 size = os.stat(tmpPath).st_size
2250 if size >= 1024:
2251 cmd += ['--continue-at', '%d' % (size,)]
2252 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002253 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002254 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2255 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002256 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002257 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002258 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002259 if srcUrl.startswith('persistent-'):
2260 srcUrl = srcUrl[len('persistent-'):]
2261 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002262
Dave Borowitz137d0132015-01-02 11:12:54 -08002263 if IsTrace():
2264 Trace('%s', ' '.join(cmd))
2265 try:
2266 proc = subprocess.Popen(cmd)
2267 except OSError:
2268 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002269
Dave Borowitz137d0132015-01-02 11:12:54 -08002270 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002271
Dave Borowitz137d0132015-01-02 11:12:54 -08002272 if curlret == 22:
2273 # From curl man page:
2274 # 22: HTTP page not retrieved. The requested url was not found or
2275 # returned another error with the HTTP error code being 400 or above.
2276 # This return code only appears if -f, --fail is used.
2277 if not quiet:
2278 print("Server does not provide clone.bundle; ignoring.",
2279 file=sys.stderr)
2280 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002281
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002282 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002283 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002284 os.rename(tmpPath, dstPath)
2285 return True
2286 else:
2287 os.remove(tmpPath)
2288 return False
2289 else:
2290 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002291
Kris Giesingc8d882a2014-12-23 13:02:32 -08002292 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002293 try:
2294 with open(path) as f:
2295 if f.read(16) == '# v2 git bundle\n':
2296 return True
2297 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002298 if not quiet:
2299 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002300 return False
2301 except OSError:
2302 return False
2303
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002304 def _Checkout(self, rev, quiet=False):
2305 cmd = ['checkout']
2306 if quiet:
2307 cmd.append('-q')
2308 cmd.append(rev)
2309 cmd.append('--')
2310 if GitCommand(self, cmd).Wait() != 0:
2311 if self._allrefs:
2312 raise GitError('%s checkout %s ' % (self.name, rev))
2313
Anthony King7bdac712014-07-16 12:56:40 +01002314 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002315 cmd = ['cherry-pick']
2316 cmd.append(rev)
2317 cmd.append('--')
2318 if GitCommand(self, cmd).Wait() != 0:
2319 if self._allrefs:
2320 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2321
Anthony King7bdac712014-07-16 12:56:40 +01002322 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002323 cmd = ['revert']
2324 cmd.append('--no-edit')
2325 cmd.append(rev)
2326 cmd.append('--')
2327 if GitCommand(self, cmd).Wait() != 0:
2328 if self._allrefs:
2329 raise GitError('%s revert %s ' % (self.name, rev))
2330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331 def _ResetHard(self, rev, quiet=True):
2332 cmd = ['reset', '--hard']
2333 if quiet:
2334 cmd.append('-q')
2335 cmd.append(rev)
2336 if GitCommand(self, cmd).Wait() != 0:
2337 raise GitError('%s reset --hard %s ' % (self.name, rev))
2338
Martin Kellye4e94d22017-03-21 16:05:12 -07002339 def _SyncSubmodules(self, quiet=True):
2340 cmd = ['submodule', 'update', '--init', '--recursive']
2341 if quiet:
2342 cmd.append('-q')
2343 if GitCommand(self, cmd).Wait() != 0:
2344 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2345
Anthony King7bdac712014-07-16 12:56:40 +01002346 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002347 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348 if onto is not None:
2349 cmd.extend(['--onto', onto])
2350 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002351 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352 raise GitError('%s rebase %s ' % (self.name, upstream))
2353
Pierre Tardy3d125942012-05-04 12:18:12 +02002354 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002355 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002356 if ffonly:
2357 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358 if GitCommand(self, cmd).Wait() != 0:
2359 raise GitError('%s merge %s ' % (self.name, head))
2360
Kevin Degiabaa7f32014-11-12 11:27:45 -07002361 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002362 init_git_dir = not os.path.exists(self.gitdir)
2363 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002364 try:
2365 # Initialize the bare repository, which contains all of the objects.
2366 if init_obj_dir:
2367 os.makedirs(self.objdir)
2368 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002369
Kevin Degib1a07b82015-07-27 13:33:43 -06002370 # If we have a separate directory to hold refs, initialize it as well.
2371 if self.objdir != self.gitdir:
2372 if init_git_dir:
2373 os.makedirs(self.gitdir)
2374
2375 if init_obj_dir or init_git_dir:
2376 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2377 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002378 try:
2379 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2380 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002381 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002382 print("Retrying clone after deleting %s" %
2383 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002384 try:
2385 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002386 if self.worktree and os.path.exists(os.path.realpath
2387 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002388 shutil.rmtree(os.path.realpath(self.worktree))
2389 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2390 except:
2391 raise e
2392 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002393
Kevin Degi384b3c52014-10-16 16:02:58 -06002394 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002395 mp = self.manifest.manifestProject
2396 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002397
Kevin Degib1a07b82015-07-27 13:33:43 -06002398 if ref_dir or mirror_git:
2399 if not mirror_git:
2400 mirror_git = os.path.join(ref_dir, self.name + '.git')
2401 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2402 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002403
Kevin Degib1a07b82015-07-27 13:33:43 -06002404 if os.path.exists(mirror_git):
2405 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406
Kevin Degib1a07b82015-07-27 13:33:43 -06002407 elif os.path.exists(repo_git):
2408 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002409
Kevin Degib1a07b82015-07-27 13:33:43 -06002410 else:
2411 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002412
Kevin Degib1a07b82015-07-27 13:33:43 -06002413 if ref_dir:
2414 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2415 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002416
Kevin Degib1a07b82015-07-27 13:33:43 -06002417 self._UpdateHooks()
2418
2419 m = self.manifest.manifestProject.config
2420 for key in ['user.name', 'user.email']:
2421 if m.Has(key, include_defaults=False):
2422 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002423 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002424 if self.manifest.IsMirror:
2425 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002426 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002427 self.config.SetString('core.bare', None)
2428 except Exception:
2429 if init_obj_dir and os.path.exists(self.objdir):
2430 shutil.rmtree(self.objdir)
2431 if init_git_dir and os.path.exists(self.gitdir):
2432 shutil.rmtree(self.gitdir)
2433 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002434
Jimmie Westera0444582012-10-24 13:44:42 +02002435 def _UpdateHooks(self):
2436 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002437 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002438
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002439 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002440 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002441 if not os.path.exists(hooks):
2442 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002443 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002444 name = os.path.basename(stock_hook)
2445
Victor Boivie65e0f352011-04-18 11:23:29 +02002446 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002447 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002448 # Don't install a Gerrit Code Review hook if this
2449 # project does not appear to use it for reviews.
2450 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002451 # Since the manifest project is one of those, but also
2452 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002453 continue
2454
2455 dst = os.path.join(hooks, name)
2456 if os.path.islink(dst):
2457 continue
2458 if os.path.exists(dst):
2459 if filecmp.cmp(stock_hook, dst, shallow=False):
2460 os.remove(dst)
2461 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002462 _warn("%s: Not replacing locally modified %s hook",
2463 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002464 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002465 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002466 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002467 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002468 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002469 raise GitError('filesystem must support symlinks')
2470 else:
2471 raise
2472
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002473 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002474 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002476 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002477 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002478 remote.review = self.remote.review
2479 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002481 if self.worktree:
2482 remote.ResetFetch(mirror=False)
2483 else:
2484 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002485 remote.Save()
2486
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002487 def _InitMRef(self):
2488 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002489 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002491 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002492 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002493
2494 def _InitAnyMRef(self, ref):
2495 cur = self.bare_ref.symref(ref)
2496
2497 if self.revisionId:
2498 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2499 msg = 'manifest set to %s' % self.revisionId
2500 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002501 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002502 else:
2503 remote = self.GetRemote(self.remote.name)
2504 dst = remote.ToLocal(self.revisionExpr)
2505 if cur != dst:
2506 msg = 'manifest set to %s' % self.revisionExpr
2507 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002508
Kevin Degi384b3c52014-10-16 16:02:58 -06002509 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002510 symlink_files = self.shareable_files[:]
2511 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002512 if share_refs:
2513 symlink_files += self.working_tree_files
2514 symlink_dirs += self.working_tree_dirs
2515 to_symlink = symlink_files + symlink_dirs
2516 for name in set(to_symlink):
2517 dst = os.path.realpath(os.path.join(destdir, name))
2518 if os.path.lexists(dst):
2519 src = os.path.realpath(os.path.join(srcdir, name))
2520 # Fail if the links are pointing to the wrong place
2521 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002522 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002523 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002524 'work tree. If you\'re comfortable with the '
2525 'possibility of losing the work tree\'s git metadata,'
2526 ' use `repo sync --force-sync {0}` to '
2527 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002528
David James8d201162013-10-11 17:03:19 -07002529 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2530 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2531
2532 Args:
2533 gitdir: The bare git repository. Must already be initialized.
2534 dotgit: The repository you would like to initialize.
2535 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2536 Only one work tree can store refs under a given |gitdir|.
2537 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2538 This saves you the effort of initializing |dotgit| yourself.
2539 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002540 symlink_files = self.shareable_files[:]
2541 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002542 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002543 symlink_files += self.working_tree_files
2544 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002545 to_symlink = symlink_files + symlink_dirs
2546
2547 to_copy = []
2548 if copy_all:
2549 to_copy = os.listdir(gitdir)
2550
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002551 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002552 for name in set(to_copy).union(to_symlink):
2553 try:
2554 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002555 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002556
Kevin Degi384b3c52014-10-16 16:02:58 -06002557 if os.path.lexists(dst):
2558 continue
David James8d201162013-10-11 17:03:19 -07002559
2560 # If the source dir doesn't exist, create an empty dir.
2561 if name in symlink_dirs and not os.path.lexists(src):
2562 os.makedirs(src)
2563
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002564 if name in to_symlink:
2565 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2566 elif copy_all and not os.path.islink(dst):
2567 if os.path.isdir(src):
2568 shutil.copytree(src, dst)
2569 elif os.path.isfile(src):
2570 shutil.copy(src, dst)
2571
Conley Owens80b87fe2014-05-09 17:13:44 -07002572 # If the source file doesn't exist, ensure the destination
2573 # file doesn't either.
2574 if name in symlink_files and not os.path.lexists(src):
2575 try:
2576 os.remove(dst)
2577 except OSError:
2578 pass
2579
David James8d201162013-10-11 17:03:19 -07002580 except OSError as e:
2581 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002582 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002583 else:
2584 raise
2585
Martin Kellye4e94d22017-03-21 16:05:12 -07002586 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002588 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002589 try:
2590 if init_dotgit:
2591 os.makedirs(dotgit)
2592 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2593 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002594
Kevin Degiabaa7f32014-11-12 11:27:45 -07002595 try:
2596 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2597 except GitError as e:
2598 if force_sync:
2599 try:
2600 shutil.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002601 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002602 except:
2603 raise e
2604 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002605
Kevin Degib1a07b82015-07-27 13:33:43 -06002606 if init_dotgit:
2607 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608
Kevin Degib1a07b82015-07-27 13:33:43 -06002609 cmd = ['read-tree', '--reset', '-u']
2610 cmd.append('-v')
2611 cmd.append(HEAD)
2612 if GitCommand(self, cmd).Wait() != 0:
2613 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002614
Martin Kellye4e94d22017-03-21 16:05:12 -07002615 if submodules:
2616 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002617 self._CopyAndLinkFiles()
2618 except Exception:
2619 if init_dotgit:
2620 shutil.rmtree(dotgit)
2621 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622
2623 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002624 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002625
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002626 def _revlist(self, *args, **kw):
2627 a = []
2628 a.extend(args)
2629 a.append('--')
2630 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002631
2632 @property
2633 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002634 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002635
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002636 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002637 """Get logs between two revisions of this project."""
2638 comp = '..'
2639 if rev1:
2640 revs = [rev1]
2641 if rev2:
2642 revs.extend([comp, rev2])
2643 cmd = ['log', ''.join(revs)]
2644 out = DiffColoring(self.config)
2645 if out.is_on and color:
2646 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002647 if pretty_format is not None:
2648 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002649 if oneline:
2650 cmd.append('--oneline')
2651
2652 try:
2653 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2654 if log.Wait() == 0:
2655 return log.stdout
2656 except GitError:
2657 # worktree may not exist if groups changed for example. In that case,
2658 # try in gitdir instead.
2659 if not os.path.exists(self.worktree):
2660 return self.bare_git.log(*cmd[1:])
2661 else:
2662 raise
2663 return None
2664
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002665 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2666 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002667 """Get the list of logs from this revision to given revisionId"""
2668 logs = {}
2669 selfId = self.GetRevisionId(self._allrefs)
2670 toId = toProject.GetRevisionId(toProject._allrefs)
2671
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002672 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2673 pretty_format=pretty_format)
2674 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2675 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002676 return logs
2677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002679
David James8d201162013-10-11 17:03:19 -07002680 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002681 self._project = project
2682 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002683 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002684
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002685 def LsOthers(self):
2686 p = GitCommand(self._project,
2687 ['ls-files',
2688 '-z',
2689 '--others',
2690 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002691 bare=False,
David James8d201162013-10-11 17:03:19 -07002692 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002693 capture_stdout=True,
2694 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002695 if p.Wait() == 0:
2696 out = p.stdout
2697 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002698 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002699 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700 return []
2701
2702 def DiffZ(self, name, *args):
2703 cmd = [name]
2704 cmd.append('-z')
2705 cmd.extend(args)
2706 p = GitCommand(self._project,
2707 cmd,
David James8d201162013-10-11 17:03:19 -07002708 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002709 bare=False,
2710 capture_stdout=True,
2711 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002712 try:
2713 out = p.process.stdout.read()
2714 r = {}
2715 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002716 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002717 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002718 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002719 info = next(out)
2720 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002721 except StopIteration:
2722 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002723
2724 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002725
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002726 def __init__(self, path, omode, nmode, oid, nid, state):
2727 self.path = path
2728 self.src_path = None
2729 self.old_mode = omode
2730 self.new_mode = nmode
2731 self.old_id = oid
2732 self.new_id = nid
2733
2734 if len(state) == 1:
2735 self.status = state
2736 self.level = None
2737 else:
2738 self.status = state[:1]
2739 self.level = state[1:]
2740 while self.level.startswith('0'):
2741 self.level = self.level[1:]
2742
2743 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002744 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002745 if info.status in ('R', 'C'):
2746 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002747 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748 r[info.path] = info
2749 return r
2750 finally:
2751 p.Wait()
2752
2753 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002754 if self._bare:
2755 path = os.path.join(self._project.gitdir, HEAD)
2756 else:
2757 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002758 try:
2759 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002760 except IOError as e:
2761 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002762 try:
2763 line = fd.read()
2764 finally:
2765 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302766 try:
2767 line = line.decode()
2768 except AttributeError:
2769 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002770 if line.startswith('ref: '):
2771 return line[5:-1]
2772 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002773
2774 def SetHead(self, ref, message=None):
2775 cmdv = []
2776 if message is not None:
2777 cmdv.extend(['-m', message])
2778 cmdv.append(HEAD)
2779 cmdv.append(ref)
2780 self.symbolic_ref(*cmdv)
2781
2782 def DetachHead(self, new, message=None):
2783 cmdv = ['--no-deref']
2784 if message is not None:
2785 cmdv.extend(['-m', message])
2786 cmdv.append(HEAD)
2787 cmdv.append(new)
2788 self.update_ref(*cmdv)
2789
2790 def UpdateRef(self, name, new, old=None,
2791 message=None,
2792 detach=False):
2793 cmdv = []
2794 if message is not None:
2795 cmdv.extend(['-m', message])
2796 if detach:
2797 cmdv.append('--no-deref')
2798 cmdv.append(name)
2799 cmdv.append(new)
2800 if old is not None:
2801 cmdv.append(old)
2802 self.update_ref(*cmdv)
2803
2804 def DeleteRef(self, name, old=None):
2805 if not old:
2806 old = self.rev_parse(name)
2807 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002808 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002810 def rev_list(self, *args, **kw):
2811 if 'format' in kw:
2812 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2813 else:
2814 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002815 cmdv.extend(args)
2816 p = GitCommand(self._project,
2817 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002818 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002819 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002820 capture_stdout=True,
2821 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002822 r = []
2823 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002824 if line[-1] == '\n':
2825 line = line[:-1]
2826 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002827 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002828 raise GitError('%s rev-list %s: %s' %
2829 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002830 return r
2831
2832 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002833 """Allow arbitrary git commands using pythonic syntax.
2834
2835 This allows you to do things like:
2836 git_obj.rev_parse('HEAD')
2837
2838 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2839 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002840 Any other positional arguments will be passed to the git command, and the
2841 following keyword arguments are supported:
2842 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002843
2844 Args:
2845 name: The name of the git command to call. Any '_' characters will
2846 be replaced with '-'.
2847
2848 Returns:
2849 A callable object that will try to call git with the named command.
2850 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002851 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002852
Dave Borowitz091f8932012-10-23 17:01:04 -07002853 def runner(*args, **kwargs):
2854 cmdv = []
2855 config = kwargs.pop('config', None)
2856 for k in kwargs:
2857 raise TypeError('%s() got an unexpected keyword argument %r'
2858 % (name, k))
2859 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002860 if not git_require((1, 7, 2)):
2861 raise ValueError('cannot set config on command line for %s()'
2862 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302863 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002864 cmdv.append('-c')
2865 cmdv.append('%s=%s' % (k, v))
2866 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002867 cmdv.extend(args)
2868 p = GitCommand(self._project,
2869 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002870 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002871 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002872 capture_stdout=True,
2873 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002874 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002875 raise GitError('%s %s: %s' %
2876 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002877 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302878 try:
Conley Owensedd01512013-09-26 12:59:58 -07002879 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302880 except AttributeError:
2881 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002882 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2883 return r[:-1]
2884 return r
2885 return runner
2886
2887
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002888class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002889
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002890 def __str__(self):
2891 return 'prior sync failed; rebase still in progress'
2892
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002893
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002894class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002895
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002896 def __str__(self):
2897 return 'contains uncommitted changes'
2898
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002899
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002900class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002901
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002902 def __init__(self, project, text):
2903 self.project = project
2904 self.text = text
2905
2906 def Print(self, syncbuf):
2907 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2908 syncbuf.out.nl()
2909
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002910
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002911class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002912
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002913 def __init__(self, project, why):
2914 self.project = project
2915 self.why = why
2916
2917 def Print(self, syncbuf):
2918 syncbuf.out.fail('error: %s/: %s',
2919 self.project.relpath,
2920 str(self.why))
2921 syncbuf.out.nl()
2922
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002923
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002924class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002925
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002926 def __init__(self, project, action):
2927 self.project = project
2928 self.action = action
2929
2930 def Run(self, syncbuf):
2931 out = syncbuf.out
2932 out.project('project %s/', self.project.relpath)
2933 out.nl()
2934 try:
2935 self.action()
2936 out.nl()
2937 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002938 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002939 out.nl()
2940 return False
2941
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002942
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002943class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002944
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002945 def __init__(self, config):
2946 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002947 self.project = self.printer('header', attr='bold')
2948 self.info = self.printer('info')
2949 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002950
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002951
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002952class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002953
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002954 def __init__(self, config, detach_head=False):
2955 self._messages = []
2956 self._failures = []
2957 self._later_queue1 = []
2958 self._later_queue2 = []
2959
2960 self.out = _SyncColoring(config)
2961 self.out.redirect(sys.stderr)
2962
2963 self.detach_head = detach_head
2964 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002965 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002966
2967 def info(self, project, fmt, *args):
2968 self._messages.append(_InfoMessage(project, fmt % args))
2969
2970 def fail(self, project, err=None):
2971 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002972 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002973
2974 def later1(self, project, what):
2975 self._later_queue1.append(_Later(project, what))
2976
2977 def later2(self, project, what):
2978 self._later_queue2.append(_Later(project, what))
2979
2980 def Finish(self):
2981 self._PrintMessages()
2982 self._RunLater()
2983 self._PrintMessages()
2984 return self.clean
2985
David Rileye0684ad2017-04-05 00:02:59 -07002986 def Recently(self):
2987 recent_clean = self.recent_clean
2988 self.recent_clean = True
2989 return recent_clean
2990
2991 def _MarkUnclean(self):
2992 self.clean = False
2993 self.recent_clean = False
2994
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002995 def _RunLater(self):
2996 for q in ['_later_queue1', '_later_queue2']:
2997 if not self._RunQueue(q):
2998 return
2999
3000 def _RunQueue(self, queue):
3001 for m in getattr(self, queue):
3002 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003003 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003004 return False
3005 setattr(self, queue, [])
3006 return True
3007
3008 def _PrintMessages(self):
3009 for m in self._messages:
3010 m.Print(self)
3011 for m in self._failures:
3012 m.Print(self)
3013
3014 self._messages = []
3015 self._failures = []
3016
3017
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003018class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003019
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003020 """A special project housed under .repo.
3021 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003022
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003023 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003024 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003025 manifest=manifest,
3026 name=name,
3027 gitdir=gitdir,
3028 objdir=gitdir,
3029 worktree=worktree,
3030 remote=RemoteSpec('origin'),
3031 relpath='.repo/%s' % name,
3032 revisionExpr='refs/heads/master',
3033 revisionId=None,
3034 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003035
3036 def PreSync(self):
3037 if self.Exists:
3038 cb = self.CurrentBranch
3039 if cb:
3040 base = self.GetBranch(cb).merge
3041 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003042 self.revisionExpr = base
3043 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003044
Martin Kelly224a31a2017-07-10 14:46:25 -07003045 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003046 """ Prepare MetaProject for manifest branch switch
3047 """
3048
3049 # detach and delete manifest branch, allowing a new
3050 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003051 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003052 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003053 syncbuf.Finish()
3054
3055 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003056 ['update-ref', '-d', 'refs/heads/default'],
3057 capture_stdout=True,
3058 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003059
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003060 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003061 def LastFetch(self):
3062 try:
3063 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3064 return os.path.getmtime(fh)
3065 except OSError:
3066 return 0
3067
3068 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003069 def HasChanges(self):
3070 """Has the remote received new commits not yet checked out?
3071 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003072 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003073 return False
3074
David Pursehouse8a68ff92012-09-24 12:15:13 +09003075 all_refs = self.bare_ref.all
3076 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003077 head = self.work_git.GetHead()
3078 if head.startswith(R_HEADS):
3079 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003080 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003081 except KeyError:
3082 head = None
3083
3084 if revid == head:
3085 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003086 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003087 return True
3088 return False