blob: 4eca9b679124dbae669fd31b741c330c9c091867 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070038import platform_utils
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070039from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Shawn O. Pearced237b692009-04-17 18:49:50 -070041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
David Pursehouse59bbb582013-05-17 10:49:33 +090043from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040044if is_python3():
45 import urllib.parse
46else:
47 import imp
48 import urlparse
49 urllib = imp.new_module('urllib')
50 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090051 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053052 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090053 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070055
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056def _lwrite(path, content):
57 lock = '%s.lock' % path
58
Chirayu Desai303a82f2014-08-19 22:57:17 +053059 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 try:
61 fd.write(content)
62 finally:
63 fd.close()
64
65 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070066 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070067 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,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000179 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200180 private=False,
181 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200182 dest_branch=None,
183 validate_certs=True):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800184 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700185 people,
Brian Harring435370c2012-07-28 15:37:04 -0700186 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000187 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200188 private=private,
189 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200190 dest_branch=dest_branch,
191 validate_certs=validate_certs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700193 def GetPublishedRefs(self):
194 refs = {}
195 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196 self.branch.remote.SshReviewUrl(self.project.UserEmail),
197 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700198 for line in output.split('\n'):
199 try:
200 (sha, ref) = line.split()
201 refs[sha] = ref
202 except ValueError:
203 pass
204
205 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700209
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 def __init__(self, config):
211 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100212 self.project = self.printer('header', attr='bold')
213 self.branch = self.printer('header', attr='bold')
214 self.nobranch = self.printer('nobranch', fg='red')
215 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Anthony King7bdac712014-07-16 12:56:40 +0100217 self.added = self.printer('added', fg='green')
218 self.changed = self.printer('changed', fg='red')
219 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
221
222class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 def __init__(self, config):
225 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100226 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228
Anthony King7bdac712014-07-16 12:56:40 +0100229class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700230
James W. Mills24c13082012-04-12 15:04:13 -0500231 def __init__(self, name, value, keep):
232 self.name = name
233 self.value = value
234 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
Anthony King7bdac712014-07-16 12:56:40 +0100237class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 self.src = src
241 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800242 self.abs_src = abssrc
243 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800246 src = self.abs_src
247 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 # copy file if it does not exist or is out of date
249 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
250 try:
251 # remove existing file first, since it might be read-only
252 if os.path.exists(dest):
253 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400254 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200255 dest_dir = os.path.dirname(dest)
256 if not os.path.isdir(dest_dir):
257 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258 shutil.copy(src, dest)
259 # make the file read-only
260 mode = os.stat(dest)[stat.ST_MODE]
261 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
262 os.chmod(dest, mode)
263 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700264 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Anthony King7bdac712014-07-16 12:56:40 +0100267class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 def __init__(self, git_worktree, src, dest, relsrc, absdest):
270 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 self.src = src
272 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700273 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500274 self.abs_dest = absdest
275
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700278 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500279 try:
280 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800281 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500283 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700284 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500285 if not os.path.isdir(dest_dir):
286 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700287 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500288 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700289 _error('Cannot link file %s to %s', relSrc, absDest)
290
291 def _Link(self):
292 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
293 on the src linking all of the files in the source in to the destination
294 directory.
295 """
296 # We use the absSrc to handle the situation where the current directory
297 # is not the root of the repo
298 absSrc = os.path.join(self.git_worktree, self.src)
299 if os.path.exists(absSrc):
300 # Entity exists so just a simple one to one link operation
301 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
302 else:
303 # Entity doesn't exist assume there is a wild card
304 absDestDir = self.abs_dest
305 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
306 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700307 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700308 else:
309 absSrcFiles = glob.glob(absSrc)
310 for absSrcFile in absSrcFiles:
311 # Create a releative path from source dir to destination dir
312 absSrcDir = os.path.dirname(absSrcFile)
313 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
314
315 # Get the source file name
316 srcFile = os.path.basename(absSrcFile)
317
318 # Now form the final full paths to srcFile. They will be
319 # absolute for the desintaiton and relative for the srouce.
320 absDest = os.path.join(absDestDir, srcFile)
321 relSrc = os.path.join(relSrcDir, srcFile)
322 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500323
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700325class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700326
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700327 def __init__(self,
328 name,
Anthony King7bdac712014-07-16 12:56:40 +0100329 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700330 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100331 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700332 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700333 orig_name=None,
334 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700335 self.name = name
336 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700337 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700338 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100339 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700340 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700341 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700343
Doug Anderson37282b42011-03-04 11:54:18 -0800344class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700345
Doug Anderson37282b42011-03-04 11:54:18 -0800346 """A RepoHook contains information about a script to run as a hook.
347
348 Hooks are used to run a python script before running an upload (for instance,
349 to run presubmit checks). Eventually, we may have hooks for other actions.
350
351 This shouldn't be confused with files in the 'repo/hooks' directory. Those
352 files are copied into each '.git/hooks' folder for each project. Repo-level
353 hooks are associated instead with repo actions.
354
355 Hooks are always python. When a hook is run, we will load the hook into the
356 interpreter and execute its main() function.
357 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700358
Doug Anderson37282b42011-03-04 11:54:18 -0800359 def __init__(self,
360 hook_type,
361 hooks_project,
362 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400363 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800364 abort_if_user_denies=False):
365 """RepoHook constructor.
366
367 Params:
368 hook_type: A string representing the type of hook. This is also used
369 to figure out the name of the file containing the hook. For
370 example: 'pre-upload'.
371 hooks_project: The project containing the repo hooks. If you have a
372 manifest, this is manifest.repo_hooks_project. OK if this is None,
373 which will make the hook a no-op.
374 topdir: Repo's top directory (the one containing the .repo directory).
375 Scripts will run with CWD as this directory. If you have a manifest,
376 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400377 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800378 abort_if_user_denies: If True, we'll throw a HookError() if the user
379 doesn't allow us to run the hook.
380 """
381 self._hook_type = hook_type
382 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400383 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800384 self._topdir = topdir
385 self._abort_if_user_denies = abort_if_user_denies
386
387 # Store the full path to the script for convenience.
388 if self._hooks_project:
389 self._script_fullpath = os.path.join(self._hooks_project.worktree,
390 self._hook_type + '.py')
391 else:
392 self._script_fullpath = None
393
394 def _GetHash(self):
395 """Return a hash of the contents of the hooks directory.
396
397 We'll just use git to do this. This hash has the property that if anything
398 changes in the directory we will return a different has.
399
400 SECURITY CONSIDERATION:
401 This hash only represents the contents of files in the hook directory, not
402 any other files imported or called by hooks. Changes to imported files
403 can change the script behavior without affecting the hash.
404
405 Returns:
406 A string representing the hash. This will always be ASCII so that it can
407 be printed to the user easily.
408 """
409 assert self._hooks_project, "Must have hooks to calculate their hash."
410
411 # We will use the work_git object rather than just calling GetRevisionId().
412 # That gives us a hash of the latest checked in version of the files that
413 # the user will actually be executing. Specifically, GetRevisionId()
414 # doesn't appear to change even if a user checks out a different version
415 # of the hooks repo (via git checkout) nor if a user commits their own revs.
416 #
417 # NOTE: Local (non-committed) changes will not be factored into this hash.
418 # I think this is OK, since we're really only worried about warning the user
419 # about upstream changes.
420 return self._hooks_project.work_git.rev_parse('HEAD')
421
422 def _GetMustVerb(self):
423 """Return 'must' if the hook is required; 'should' if not."""
424 if self._abort_if_user_denies:
425 return 'must'
426 else:
427 return 'should'
428
429 def _CheckForHookApproval(self):
430 """Check to see whether this hook has been approved.
431
Mike Frysinger40252c22016-08-15 21:23:44 -0400432 We'll accept approval of manifest URLs if they're using secure transports.
433 This way the user can say they trust the manifest hoster. For insecure
434 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800435
436 Note that we ask permission for each individual hook even though we use
437 the hash of all hooks when detecting changes. We'd like the user to be
438 able to approve / deny each hook individually. We only use the hash of all
439 hooks because there is no other easy way to detect changes to local imports.
440
441 Returns:
442 True if this hook is approved to run; False otherwise.
443
444 Raises:
445 HookError: Raised if the user doesn't approve and abort_if_user_denies
446 was passed to the consturctor.
447 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400448 if self._ManifestUrlHasSecureScheme():
449 return self._CheckForHookApprovalManifest()
450 else:
451 return self._CheckForHookApprovalHash()
452
453 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
454 changed_prompt):
455 """Check for approval for a particular attribute and hook.
456
457 Args:
458 subkey: The git config key under [repo.hooks.<hook_type>] to store the
459 last approved string.
460 new_val: The new value to compare against the last approved one.
461 main_prompt: Message to display to the user to ask for approval.
462 changed_prompt: Message explaining why we're re-asking for approval.
463
464 Returns:
465 True if this hook is approved to run; False otherwise.
466
467 Raises:
468 HookError: Raised if the user doesn't approve and abort_if_user_denies
469 was passed to the consturctor.
470 """
Doug Anderson37282b42011-03-04 11:54:18 -0800471 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800473
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 # Get the last value that the user approved for this hook; may be None.
475 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800476
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800478 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400479 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800480 # Approval matched. We're done.
481 return True
482 else:
483 # Give the user a reason why we're prompting, since they last told
484 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400485 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800486 else:
487 prompt = ''
488
489 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
490 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400491 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530492 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900493 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800494
495 # User is doing a one-time approval.
496 if response in ('y', 'yes'):
497 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400498 elif response == 'always':
499 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800500 return True
501
502 # For anything else, we'll assume no approval.
503 if self._abort_if_user_denies:
504 raise HookError('You must allow the %s hook or use --no-verify.' %
505 self._hook_type)
506
507 return False
508
Mike Frysinger40252c22016-08-15 21:23:44 -0400509 def _ManifestUrlHasSecureScheme(self):
510 """Check if the URI for the manifest is a secure transport."""
511 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
512 parse_results = urllib.parse.urlparse(self._manifest_url)
513 return parse_results.scheme in secure_schemes
514
515 def _CheckForHookApprovalManifest(self):
516 """Check whether the user has approved this manifest host.
517
518 Returns:
519 True if this hook is approved to run; False otherwise.
520 """
521 return self._CheckForHookApprovalHelper(
522 'approvedmanifest',
523 self._manifest_url,
524 'Run hook scripts from %s' % (self._manifest_url,),
525 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
526
527 def _CheckForHookApprovalHash(self):
528 """Check whether the user has approved the hooks repo.
529
530 Returns:
531 True if this hook is approved to run; False otherwise.
532 """
533 prompt = ('Repo %s run the script:\n'
534 ' %s\n'
535 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700536 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400537 return self._CheckForHookApprovalHelper(
538 'approvedhash',
539 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700540 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400541 'Scripts have changed since %s was allowed.' % (self._hook_type,))
542
Doug Anderson37282b42011-03-04 11:54:18 -0800543 def _ExecuteHook(self, **kwargs):
544 """Actually execute the given hook.
545
546 This will run the hook's 'main' function in our python interpreter.
547
548 Args:
549 kwargs: Keyword arguments to pass to the hook. These are often specific
550 to the hook type. For instance, pre-upload hooks will contain
551 a project_list.
552 """
553 # Keep sys.path and CWD stashed away so that we can always restore them
554 # upon function exit.
555 orig_path = os.getcwd()
556 orig_syspath = sys.path
557
558 try:
559 # Always run hooks with CWD as topdir.
560 os.chdir(self._topdir)
561
562 # Put the hook dir as the first item of sys.path so hooks can do
563 # relative imports. We want to replace the repo dir as [0] so
564 # hooks can't import repo files.
565 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
566
567 # Exec, storing global context in the context dict. We catch exceptions
568 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500569 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800570 try:
Anthony King70f68902014-05-05 21:15:34 +0100571 exec(compile(open(self._script_fullpath).read(),
572 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800573 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700574 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
575 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800576
577 # Running the script should have defined a main() function.
578 if 'main' not in context:
579 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
580
Doug Anderson37282b42011-03-04 11:54:18 -0800581 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
582 # We don't actually want hooks to define their main with this argument--
583 # it's there to remind them that their hook should always take **kwargs.
584 # For instance, a pre-upload hook should be defined like:
585 # def main(project_list, **kwargs):
586 #
587 # This allows us to later expand the API without breaking old hooks.
588 kwargs = kwargs.copy()
589 kwargs['hook_should_take_kwargs'] = True
590
591 # Call the main function in the hook. If the hook should cause the
592 # build to fail, it will raise an Exception. We'll catch that convert
593 # to a HookError w/ just the failing traceback.
594 try:
595 context['main'](**kwargs)
596 except Exception:
597 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700598 'above.' % (traceback.format_exc(),
599 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800600 finally:
601 # Restore sys.path and CWD.
602 sys.path = orig_syspath
603 os.chdir(orig_path)
604
605 def Run(self, user_allows_all_hooks, **kwargs):
606 """Run the hook.
607
608 If the hook doesn't exist (because there is no hooks project or because
609 this particular hook is not enabled), this is a no-op.
610
611 Args:
612 user_allows_all_hooks: If True, we will never prompt about running the
613 hook--we'll just assume it's OK to run it.
614 kwargs: Keyword arguments to pass to the hook. These are often specific
615 to the hook type. For instance, pre-upload hooks will contain
616 a project_list.
617
618 Raises:
619 HookError: If there was a problem finding the hook or the user declined
620 to run a required hook (from _CheckForHookApproval).
621 """
622 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700623 if ((not self._hooks_project) or (self._hook_type not in
624 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800625 return
626
627 # Bail with a nice error if we can't find the hook.
628 if not os.path.isfile(self._script_fullpath):
629 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
630
631 # Make sure the user is OK with running the hook.
632 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
633 return
634
635 # Run the hook with the same version of python we're using.
636 self._ExecuteHook(**kwargs)
637
638
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600640 # These objects can be shared between several working trees.
641 shareable_files = ['description', 'info']
642 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
643 # These objects can only be used by a single working tree.
644 working_tree_files = ['config', 'packed-refs', 'shallow']
645 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 def __init__(self,
648 manifest,
649 name,
650 remote,
651 gitdir,
David James8d201162013-10-11 17:03:19 -0700652 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 worktree,
654 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700655 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800656 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100657 rebase=True,
658 groups=None,
659 sync_c=False,
660 sync_s=False,
661 clone_depth=None,
662 upstream=None,
663 parent=None,
664 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900665 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700666 optimized_fetch=False,
667 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800668 """Init a Project object.
669
670 Args:
671 manifest: The XmlManifest object.
672 name: The `name` attribute of manifest.xml's project element.
673 remote: RemoteSpec object specifying its remote's properties.
674 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700675 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800676 worktree: Absolute path of git working tree.
677 relpath: Relative path of git working tree to repo's top directory.
678 revisionExpr: The `revision` attribute of manifest.xml's project element.
679 revisionId: git commit id for checking out.
680 rebase: The `rebase` attribute of manifest.xml's project element.
681 groups: The `groups` attribute of manifest.xml's project element.
682 sync_c: The `sync-c` attribute of manifest.xml's project element.
683 sync_s: The `sync-s` attribute of manifest.xml's project element.
684 upstream: The `upstream` attribute of manifest.xml's project element.
685 parent: The parent Project object.
686 is_derived: False if the project was explicitly defined in the manifest;
687 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400688 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900689 optimized_fetch: If True, when a project is set to a sha1 revision, only
690 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700691 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800692 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693 self.manifest = manifest
694 self.name = name
695 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800696 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700697 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800698 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700699 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800700 else:
701 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700703 self.revisionExpr = revisionExpr
704
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700705 if revisionId is None \
706 and revisionExpr \
707 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700708 self.revisionId = revisionExpr
709 else:
710 self.revisionId = revisionId
711
Mike Pontillod3153822012-02-28 11:53:24 -0800712 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700713 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700714 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800715 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900716 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700717 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800718 self.parent = parent
719 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900720 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800721 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800722
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500725 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500726 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700727 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
728 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800730 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700731 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800732 else:
733 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700734 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700735 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700736 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400737 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700738 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739
Doug Anderson37282b42011-03-04 11:54:18 -0800740 # This will be filled in if a project is later identified to be the
741 # project containing repo hooks.
742 self.enabled_repo_hooks = []
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800745 def Derived(self):
746 return self.is_derived
747
748 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600750 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751
752 @property
753 def CurrentBranch(self):
754 """Obtain the name of the currently checked out branch.
755 The branch name omits the 'refs/heads/' prefix.
756 None is returned if the project is on a detached HEAD.
757 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700758 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 if b.startswith(R_HEADS):
760 return b[len(R_HEADS):]
761 return None
762
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700763 def IsRebaseInProgress(self):
764 w = self.worktree
765 g = os.path.join(w, '.git')
766 return os.path.exists(os.path.join(g, 'rebase-apply')) \
767 or os.path.exists(os.path.join(g, 'rebase-merge')) \
768 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200769
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 def IsDirty(self, consider_untracked=True):
771 """Is the working directory modified in some way?
772 """
773 self.work_git.update_index('-q',
774 '--unmerged',
775 '--ignore-missing',
776 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900777 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 return True
779 if self.work_git.DiffZ('diff-files'):
780 return True
781 if consider_untracked and self.work_git.LsOthers():
782 return True
783 return False
784
785 _userident_name = None
786 _userident_email = None
787
788 @property
789 def UserName(self):
790 """Obtain the user's personal name.
791 """
792 if self._userident_name is None:
793 self._LoadUserIdentity()
794 return self._userident_name
795
796 @property
797 def UserEmail(self):
798 """Obtain the user's email address. This is very likely
799 to be their Gerrit login.
800 """
801 if self._userident_email is None:
802 self._LoadUserIdentity()
803 return self._userident_email
804
805 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900806 u = self.bare_git.var('GIT_COMMITTER_IDENT')
807 m = re.compile("^(.*) <([^>]*)> ").match(u)
808 if m:
809 self._userident_name = m.group(1)
810 self._userident_email = m.group(2)
811 else:
812 self._userident_name = ''
813 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814
815 def GetRemote(self, name):
816 """Get the configuration for a single remote.
817 """
818 return self.config.GetRemote(name)
819
820 def GetBranch(self, name):
821 """Get the configuration for a single branch.
822 """
823 return self.config.GetBranch(name)
824
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700825 def GetBranches(self):
826 """Get all existing local branches.
827 """
828 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900829 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700830 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530832 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700833 if name.startswith(R_HEADS):
834 name = name[len(R_HEADS):]
835 b = self.GetBranch(name)
836 b.current = name == current
837 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900838 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700839 heads[name] = b
840
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530841 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700842 if name.startswith(R_PUB):
843 name = name[len(R_PUB):]
844 b = heads.get(name)
845 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900846 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700847
848 return heads
849
Colin Cross5acde752012-03-28 20:15:45 -0700850 def MatchesGroups(self, manifest_groups):
851 """Returns true if the manifest groups specified at init should cause
852 this project to be synced.
853 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700854 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700855
856 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700857 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700858 manifest_groups: "-group1,group2"
859 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500860
861 The special manifest group "default" will match any project that
862 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700863 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500864 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700865 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700866 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500867 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700868
Conley Owens971de8e2012-04-16 10:36:08 -0700869 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700870 for group in expanded_manifest_groups:
871 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700872 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700873 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700874 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700875
Conley Owens971de8e2012-04-16 10:36:08 -0700876 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700878# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700879 def UncommitedFiles(self, get_all=True):
880 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700882 Args:
883 get_all: a boolean, if True - get information about all different
884 uncommitted files. If False - return as soon as any kind of
885 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500886 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700887 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500888 self.work_git.update_index('-q',
889 '--unmerged',
890 '--ignore-missing',
891 '--refresh')
892 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700893 details.append("rebase in progress")
894 if not get_all:
895 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500896
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700897 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
898 if changes:
899 details.extend(changes)
900 if not get_all:
901 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500902
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700903 changes = self.work_git.DiffZ('diff-files').keys()
904 if changes:
905 details.extend(changes)
906 if not get_all:
907 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500908
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700909 changes = self.work_git.LsOthers()
910 if changes:
911 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500912
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700913 return details
914
915 def HasChanges(self):
916 """Returns true if there are uncommitted changes.
917 """
918 if self.UncommitedFiles(get_all=False):
919 return True
920 else:
921 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500922
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600923 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200925
926 Args:
927 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600928 quiet: If True then only print the project name. Do not print
929 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 """
931 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700932 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200933 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700934 print(file=output_redir)
935 print('project %s/' % self.relpath, file=output_redir)
936 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 return
938
939 self.work_git.update_index('-q',
940 '--unmerged',
941 '--ignore-missing',
942 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700943 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
945 df = self.work_git.DiffZ('diff-files')
946 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100947 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700948 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949
950 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700951 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200952 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700953 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600955 if quiet:
956 out.nl()
957 return 'DIRTY'
958
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959 branch = self.CurrentBranch
960 if branch is None:
961 out.nobranch('(*** NO BRANCH ***)')
962 else:
963 out.branch('branch %s', branch)
964 out.nl()
965
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700966 if rb:
967 out.important('prior sync failed; rebase still in progress')
968 out.nl()
969
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 paths = list()
971 paths.extend(di.keys())
972 paths.extend(df.keys())
973 paths.extend(do)
974
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530975 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900976 try:
977 i = di[p]
978 except KeyError:
979 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900981 try:
982 f = df[p]
983 except KeyError:
984 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200985
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900986 if i:
987 i_status = i.status.upper()
988 else:
989 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900991 if f:
992 f_status = f.status.lower()
993 else:
994 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995
996 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800997 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700998 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 else:
1000 line = ' %s%s\t%s' % (i_status, f_status, p)
1001
1002 if i and not f:
1003 out.added('%s', line)
1004 elif (i and f) or (not i and f):
1005 out.changed('%s', line)
1006 elif not i and not f:
1007 out.untracked('%s', line)
1008 else:
1009 out.write('%s', line)
1010 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001011
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001012 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013
pelyad67872d2012-03-28 14:49:58 +03001014 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 """Prints the status of the repository to stdout.
1016 """
1017 out = DiffColoring(self.config)
1018 cmd = ['diff']
1019 if out.is_on:
1020 cmd.append('--color')
1021 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001022 if absolute_paths:
1023 cmd.append('--src-prefix=a/%s/' % self.relpath)
1024 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025 cmd.append('--')
1026 p = GitCommand(self,
1027 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001028 capture_stdout=True,
1029 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030 has_diff = False
1031 for line in p.process.stdout:
1032 if not has_diff:
1033 out.nl()
1034 out.project('project %s/' % self.relpath)
1035 out.nl()
1036 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001037 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 p.Wait()
1039
1040
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001041# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 """Was the branch published (uploaded) for code review?
1045 If so, returns the SHA-1 hash of the last published
1046 state for the branch.
1047 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001048 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001050 try:
1051 return self.bare_git.rev_parse(key)
1052 except GitError:
1053 return None
1054 else:
1055 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001056 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001057 except KeyError:
1058 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059
David Pursehouse8a68ff92012-09-24 12:15:13 +09001060 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 """Prunes any stale published refs.
1062 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 if all_refs is None:
1064 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 heads = set()
1066 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301067 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 if name.startswith(R_HEADS):
1069 heads.add(name)
1070 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301073 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 n = name[len(R_PUB):]
1075 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001076 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001078 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 """List any branches which can be uploaded for review.
1080 """
1081 heads = {}
1082 pubed = {}
1083
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301084 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001086 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001088 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
1090 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301091 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001092 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001094 if selected_branch and branch != selected_branch:
1095 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001097 rb = self.GetUploadableBranch(branch)
1098 if rb:
1099 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 return ready
1101
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001102 def GetUploadableBranch(self, branch_name):
1103 """Get a single uploadable branch, or None.
1104 """
1105 branch = self.GetBranch(branch_name)
1106 base = branch.LocalMerge
1107 if branch.LocalMerge:
1108 rb = ReviewableBranch(self, branch, base)
1109 if rb.commits:
1110 return rb
1111 return None
1112
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001113 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001114 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001115 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001116 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001117 private=False,
1118 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001119 dest_branch=None,
1120 validate_certs=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 """Uploads the named branch for code review.
1122 """
1123 if branch is None:
1124 branch = self.CurrentBranch
1125 if branch is None:
1126 raise GitError('not currently on a branch')
1127
1128 branch = self.GetBranch(branch)
1129 if not branch.LocalMerge:
1130 raise GitError('branch %s does not track a remote' % branch.name)
1131 if not branch.remote.review:
1132 raise GitError('remote %s has no review url' % branch.remote.name)
1133
Bryan Jacobsf609f912013-05-06 13:36:24 -04001134 if dest_branch is None:
1135 dest_branch = self.dest_branch
1136 if dest_branch is None:
1137 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138 if not dest_branch.startswith(R_HEADS):
1139 dest_branch = R_HEADS + dest_branch
1140
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001141 if not branch.remote.projectname:
1142 branch.remote.projectname = self.name
1143 branch.remote.Save()
1144
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001145 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001146 if url is None:
1147 raise UploadError('review not configured')
1148 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001149
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001150 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001151 rp = ['gerrit receive-pack']
1152 for e in people[0]:
1153 rp.append('--reviewer=%s' % sq(e))
1154 for e in people[1]:
1155 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001156 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001157
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001158 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001159
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001160 if dest_branch.startswith(R_HEADS):
1161 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001162
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001163 upload_type = 'for'
1164 if draft:
1165 upload_type = 'drafts'
1166
1167 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1168 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001169 if auto_topic:
1170 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao87984c62017-08-02 16:55:03 +02001171
Shawn Pearce45d21682013-02-28 00:35:51 -08001172 if not url.startswith('ssh://'):
1173 rp = ['r=%s' % p for p in people[0]] + \
1174 ['cc=%s' % p for p in people[1]]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001175 if private:
1176 rp = rp + ['private']
1177 if wip:
1178 rp = rp + ['wip']
Shawn Pearce45d21682013-02-28 00:35:51 -08001179 if rp:
1180 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001181 cmd.append(ref_spec)
1182
Anthony King7bdac712014-07-16 12:56:40 +01001183 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001184 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185
1186 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1187 self.bare_git.UpdateRef(R_PUB + branch.name,
1188 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001189 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
1191
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001192# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193
Julien Campergue335f5ef2013-10-16 11:02:35 +02001194 def _ExtractArchive(self, tarpath, path=None):
1195 """Extract the given tar on its current location
1196
1197 Args:
1198 - tarpath: The path to the actual tar file
1199
1200 """
1201 try:
1202 with tarfile.open(tarpath, 'r') as tar:
1203 tar.extractall(path=path)
1204 return True
1205 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001206 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001207 return False
1208
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001209 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001210 quiet=False,
1211 is_new=None,
1212 current_branch_only=False,
1213 force_sync=False,
1214 clone_bundle=True,
1215 no_tags=False,
1216 archive=False,
1217 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001218 prune=False,
1219 submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220 """Perform only the network IO portion of the sync process.
1221 Local working directory/branch state is not affected.
1222 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001223 if archive and not isinstance(self, MetaProject):
1224 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001225 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001226 return False
1227
1228 name = self.relpath.replace('\\', '/')
1229 name = name.replace('/', '_')
1230 tarpath = '%s.tar' % name
1231 topdir = self.manifest.topdir
1232
1233 try:
1234 self._FetchArchive(tarpath, cwd=topdir)
1235 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001236 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001237 return False
1238
1239 # From now on, we only need absolute tarpath
1240 tarpath = os.path.join(topdir, tarpath)
1241
1242 if not self._ExtractArchive(tarpath, path=topdir):
1243 return False
1244 try:
1245 os.remove(tarpath)
1246 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001247 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001248 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001249 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001250 if is_new is None:
1251 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001252 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001253 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001254 else:
1255 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001257
1258 if is_new:
1259 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1260 try:
1261 fd = open(alt, 'rb')
1262 try:
1263 alt_dir = fd.readline().rstrip()
1264 finally:
1265 fd.close()
1266 except IOError:
1267 alt_dir = None
1268 else:
1269 alt_dir = None
1270
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001271 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001272 and alt_dir is None \
1273 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001274 is_new = False
1275
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001276 if not current_branch_only:
1277 if self.sync_c:
1278 current_branch_only = True
1279 elif not self.manifest._loaded:
1280 # Manifest cannot check defaults until it syncs.
1281 current_branch_only = False
1282 elif self.manifest.default.sync_c:
1283 current_branch_only = True
1284
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001285 if self.clone_depth:
1286 depth = self.clone_depth
1287 else:
1288 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1289
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001290 need_to_fetch = not (optimized_fetch and
1291 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001292 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001293 if (need_to_fetch and
1294 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1295 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001296 no_tags=no_tags, prune=prune, depth=depth,
1297 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001298 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001299
1300 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001301 self._InitMRef()
1302 else:
1303 self._InitMirrorHead()
1304 try:
1305 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1306 except OSError:
1307 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001309
1310 def PostRepoUpgrade(self):
1311 self._InitHooks()
1312
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001314 if self.manifest.isGitcClient:
1315 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001316 for copyfile in self.copyfiles:
1317 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001318 for linkfile in self.linkfiles:
1319 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320
Julien Camperguedd654222014-01-09 16:21:37 +01001321 def GetCommitRevisionId(self):
1322 """Get revisionId of a commit.
1323
1324 Use this method instead of GetRevisionId to get the id of the commit rather
1325 than the id of the current git object (for example, a tag)
1326
1327 """
1328 if not self.revisionExpr.startswith(R_TAGS):
1329 return self.GetRevisionId(self._allrefs)
1330
1331 try:
1332 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1333 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001334 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1335 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001336
David Pursehouse8a68ff92012-09-24 12:15:13 +09001337 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 if self.revisionId:
1339 return self.revisionId
1340
1341 rem = self.GetRemote(self.remote.name)
1342 rev = rem.ToLocal(self.revisionExpr)
1343
David Pursehouse8a68ff92012-09-24 12:15:13 +09001344 if all_refs is not None and rev in all_refs:
1345 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346
1347 try:
1348 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1349 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001350 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1351 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001352
Martin Kellye4e94d22017-03-21 16:05:12 -07001353 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354 """Perform only the local IO portion of the sync process.
1355 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001357 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001358 all_refs = self.bare_ref.all
1359 self.CleanPublishedCache(all_refs)
1360 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001361
David Pursehouse1d947b32012-10-25 12:23:11 +09001362 def _doff():
1363 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001364 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001365
Martin Kellye4e94d22017-03-21 16:05:12 -07001366 def _dosubmodules():
1367 self._SyncSubmodules(quiet=True)
1368
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001369 head = self.work_git.GetHead()
1370 if head.startswith(R_HEADS):
1371 branch = head[len(R_HEADS):]
1372 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001373 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001374 except KeyError:
1375 head = None
1376 else:
1377 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001379 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 # Currently on a detached HEAD. The user is assumed to
1381 # not have any local modifications worth worrying about.
1382 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001383 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 syncbuf.fail(self, _PriorSyncFailedError())
1385 return
1386
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001387 if head == revid:
1388 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001389 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001390 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001391 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001392 # The copy/linkfile config may have changed.
1393 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001394 return
1395 else:
1396 lost = self._revlist(not_rev(revid), HEAD)
1397 if lost:
1398 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001399
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001401 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001402 if submodules:
1403 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001404 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001405 syncbuf.fail(self, e)
1406 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001407 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001408 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001410 if head == revid:
1411 # No changes; don't do anything further.
1412 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001413 # The copy/linkfile config may have changed.
1414 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001415 return
1416
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001417 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001418
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001419 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001421 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001423 syncbuf.info(self,
1424 "leaving %s; does not track upstream",
1425 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001427 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001428 if submodules:
1429 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001430 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001431 syncbuf.fail(self, e)
1432 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001433 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001434 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001436 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001437 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001438 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001439 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001440 if not_merged:
1441 if upstream_gain:
1442 # The user has published this branch and some of those
1443 # commits are not yet merged upstream. We do not want
1444 # to rewrite the published commits so we punt.
1445 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001446 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001447 "branch %s is published (but not merged) and is now "
1448 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001449 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001450 elif pub == head:
1451 # All published commits are merged, and thus we are a
1452 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001453 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001454 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001455 if submodules:
1456 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001457 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001459 # Examine the local commits not in the remote. Find the
1460 # last one attributed to this user, if any.
1461 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001462 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001463 last_mine = None
1464 cnt_mine = 0
1465 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301466 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001467 if committer_email == self.UserEmail:
1468 last_mine = commit_id
1469 cnt_mine += 1
1470
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001471 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001472 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473
1474 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001475 syncbuf.fail(self, _DirtyError())
1476 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001477
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001478 # If the upstream switched on us, warn the user.
1479 #
1480 if branch.merge != self.revisionExpr:
1481 if branch.merge and self.revisionExpr:
1482 syncbuf.info(self,
1483 'manifest switched %s...%s',
1484 branch.merge,
1485 self.revisionExpr)
1486 elif branch.merge:
1487 syncbuf.info(self,
1488 'manifest no longer tracks %s',
1489 branch.merge)
1490
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001491 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001493 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001495 syncbuf.info(self,
1496 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001497 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001499 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001500 if not ID_RE.match(self.revisionExpr):
1501 # in case of manifest sync the revisionExpr might be a SHA1
1502 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001503 if not branch.merge.startswith('refs/'):
1504 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505 branch.Save()
1506
Mike Pontillod3153822012-02-28 11:53:24 -08001507 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001508 def _docopyandlink():
1509 self._CopyAndLinkFiles()
1510
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001511 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001512 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001513 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001514 if submodules:
1515 syncbuf.later2(self, _dosubmodules)
1516 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001517 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001518 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001519 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001520 if submodules:
1521 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001522 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001523 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001524 syncbuf.fail(self, e)
1525 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001527 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001528 if submodules:
1529 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001531 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 # dest should already be an absolute path, but src is project relative
1533 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001534 abssrc = os.path.join(self.worktree, src)
1535 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001536
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001537 def AddLinkFile(self, src, dest, absdest):
1538 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001539 # make src relative path to dest
1540 absdestdir = os.path.dirname(absdest)
1541 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001542 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001543
James W. Mills24c13082012-04-12 15:04:13 -05001544 def AddAnnotation(self, name, value, keep):
1545 self.annotations.append(_Annotation(name, value, keep))
1546
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001547 def DownloadPatchSet(self, change_id, patch_id):
1548 """Download a single patch set of a single change to FETCH_HEAD.
1549 """
1550 remote = self.GetRemote(self.remote.name)
1551
1552 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001553 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001554 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001555 if GitCommand(self, cmd, bare=True).Wait() != 0:
1556 return None
1557 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001558 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001559 change_id,
1560 patch_id,
1561 self.bare_git.rev_parse('FETCH_HEAD'))
1562
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001564# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565
Simran Basib9a1b732015-08-20 12:19:28 -07001566 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 """Create a new branch off the manifest's revision.
1568 """
Simran Basib9a1b732015-08-20 12:19:28 -07001569 if not branch_merge:
1570 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001571 head = self.work_git.GetHead()
1572 if head == (R_HEADS + name):
1573 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574
David Pursehouse8a68ff92012-09-24 12:15:13 +09001575 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001576 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001577 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001578 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001579 capture_stdout=True,
1580 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001581
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001582 branch = self.GetBranch(name)
1583 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001584 branch.merge = branch_merge
1585 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1586 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001587 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001588
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001589 if head.startswith(R_HEADS):
1590 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001591 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001592 except KeyError:
1593 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001594 if revid and head and revid == head:
1595 ref = os.path.join(self.gitdir, R_HEADS + name)
1596 try:
1597 os.makedirs(os.path.dirname(ref))
1598 except OSError:
1599 pass
1600 _lwrite(ref, '%s\n' % revid)
1601 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1602 'ref: %s%s\n' % (R_HEADS, name))
1603 branch.Save()
1604 return True
1605
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001606 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001607 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001608 capture_stdout=True,
1609 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001610 branch.Save()
1611 return True
1612 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001613
Wink Saville02d79452009-04-10 13:01:24 -07001614 def CheckoutBranch(self, name):
1615 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001616
1617 Args:
1618 name: The name of the branch to checkout.
1619
1620 Returns:
1621 True if the checkout succeeded; False if it didn't; None if the branch
1622 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001623 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001624 rev = R_HEADS + name
1625 head = self.work_git.GetHead()
1626 if head == rev:
1627 # Already on the branch
1628 #
1629 return True
Wink Saville02d79452009-04-10 13:01:24 -07001630
David Pursehouse8a68ff92012-09-24 12:15:13 +09001631 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001632 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001633 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001634 except KeyError:
1635 # Branch does not exist in this project
1636 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001637 return None
Wink Saville02d79452009-04-10 13:01:24 -07001638
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001639 if head.startswith(R_HEADS):
1640 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001641 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001642 except KeyError:
1643 head = None
1644
1645 if head == revid:
1646 # Same revision; just update HEAD to point to the new
1647 # target branch, but otherwise take no other action.
1648 #
1649 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1650 'ref: %s%s\n' % (R_HEADS, name))
1651 return True
1652
1653 return GitCommand(self,
1654 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001655 capture_stdout=True,
1656 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001657
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001658 def AbandonBranch(self, name):
1659 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001660
1661 Args:
1662 name: The name of the branch to abandon.
1663
1664 Returns:
1665 True if the abandon succeeded; False if it didn't; None if the branch
1666 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001667 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001668 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001669 all_refs = self.bare_ref.all
1670 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001671 # Doesn't exist
1672 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001673
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001674 head = self.work_git.GetHead()
1675 if head == rev:
1676 # We can't destroy the branch while we are sitting
1677 # on it. Switch to a detached HEAD.
1678 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001679 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001680
David Pursehouse8a68ff92012-09-24 12:15:13 +09001681 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001682 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001683 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1684 '%s\n' % revid)
1685 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001686 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001687
1688 return GitCommand(self,
1689 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001690 capture_stdout=True,
1691 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001692
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693 def PruneHeads(self):
1694 """Prune any topic branches already merged into upstream.
1695 """
1696 cb = self.CurrentBranch
1697 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001698 left = self._allrefs
1699 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700 if name.startswith(R_HEADS):
1701 name = name[len(R_HEADS):]
1702 if cb is None or name != cb:
1703 kill.append(name)
1704
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001705 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 if cb is not None \
1707 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001708 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001709 self.work_git.DetachHead(HEAD)
1710 kill.append(cb)
1711
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001713 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001714
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715 try:
1716 self.bare_git.DetachHead(rev)
1717
1718 b = ['branch', '-d']
1719 b.extend(kill)
1720 b = GitCommand(self, b, bare=True,
1721 capture_stdout=True,
1722 capture_stderr=True)
1723 b.Wait()
1724 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001725 if ID_RE.match(old):
1726 self.bare_git.DetachHead(old)
1727 else:
1728 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001729 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001731 for branch in kill:
1732 if (R_HEADS + branch) not in left:
1733 self.CleanPublishedCache()
1734 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735
1736 if cb and cb not in kill:
1737 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001738 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739
1740 kept = []
1741 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001742 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 branch = self.GetBranch(branch)
1744 base = branch.LocalMerge
1745 if not base:
1746 base = rev
1747 kept.append(ReviewableBranch(self, branch, base))
1748 return kept
1749
1750
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001751# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001752
1753 def GetRegisteredSubprojects(self):
1754 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001755
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001756 def rec(subprojects):
1757 if not subprojects:
1758 return
1759 result.extend(subprojects)
1760 for p in subprojects:
1761 rec(p.subprojects)
1762 rec(self.subprojects)
1763 return result
1764
1765 def _GetSubmodules(self):
1766 # Unfortunately we cannot call `git submodule status --recursive` here
1767 # because the working tree might not exist yet, and it cannot be used
1768 # without a working tree in its current implementation.
1769
1770 def get_submodules(gitdir, rev):
1771 # Parse .gitmodules for submodule sub_paths and sub_urls
1772 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1773 if not sub_paths:
1774 return []
1775 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1776 # revision of submodule repository
1777 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1778 submodules = []
1779 for sub_path, sub_url in zip(sub_paths, sub_urls):
1780 try:
1781 sub_rev = sub_revs[sub_path]
1782 except KeyError:
1783 # Ignore non-exist submodules
1784 continue
1785 submodules.append((sub_rev, sub_path, sub_url))
1786 return submodules
1787
1788 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1789 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001790
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001791 def parse_gitmodules(gitdir, rev):
1792 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1793 try:
Anthony King7bdac712014-07-16 12:56:40 +01001794 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1795 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001796 except GitError:
1797 return [], []
1798 if p.Wait() != 0:
1799 return [], []
1800
1801 gitmodules_lines = []
1802 fd, temp_gitmodules_path = tempfile.mkstemp()
1803 try:
1804 os.write(fd, p.stdout)
1805 os.close(fd)
1806 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001807 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1808 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001809 if p.Wait() != 0:
1810 return [], []
1811 gitmodules_lines = p.stdout.split('\n')
1812 except GitError:
1813 return [], []
1814 finally:
1815 os.remove(temp_gitmodules_path)
1816
1817 names = set()
1818 paths = {}
1819 urls = {}
1820 for line in gitmodules_lines:
1821 if not line:
1822 continue
1823 m = re_path.match(line)
1824 if m:
1825 names.add(m.group(1))
1826 paths[m.group(1)] = m.group(2)
1827 continue
1828 m = re_url.match(line)
1829 if m:
1830 names.add(m.group(1))
1831 urls[m.group(1)] = m.group(2)
1832 continue
1833 names = sorted(names)
1834 return ([paths.get(name, '') for name in names],
1835 [urls.get(name, '') for name in names])
1836
1837 def git_ls_tree(gitdir, rev, paths):
1838 cmd = ['ls-tree', rev, '--']
1839 cmd.extend(paths)
1840 try:
Anthony King7bdac712014-07-16 12:56:40 +01001841 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1842 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001843 except GitError:
1844 return []
1845 if p.Wait() != 0:
1846 return []
1847 objects = {}
1848 for line in p.stdout.split('\n'):
1849 if not line.strip():
1850 continue
1851 object_rev, object_path = line.split()[2:4]
1852 objects[object_path] = object_rev
1853 return objects
1854
1855 try:
1856 rev = self.GetRevisionId()
1857 except GitError:
1858 return []
1859 return get_submodules(self.gitdir, rev)
1860
1861 def GetDerivedSubprojects(self):
1862 result = []
1863 if not self.Exists:
1864 # If git repo does not exist yet, querying its submodules will
1865 # mess up its states; so return here.
1866 return result
1867 for rev, path, url in self._GetSubmodules():
1868 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001869 relpath, worktree, gitdir, objdir = \
1870 self.manifest.GetSubprojectPaths(self, name, path)
1871 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001872 if project:
1873 result.extend(project.GetDerivedSubprojects())
1874 continue
David James8d201162013-10-11 17:03:19 -07001875
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001876 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001877 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001878 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001879 review=self.remote.review,
1880 revision=self.remote.revision)
1881 subproject = Project(manifest=self.manifest,
1882 name=name,
1883 remote=remote,
1884 gitdir=gitdir,
1885 objdir=objdir,
1886 worktree=worktree,
1887 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001888 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001889 revisionId=rev,
1890 rebase=self.rebase,
1891 groups=self.groups,
1892 sync_c=self.sync_c,
1893 sync_s=self.sync_s,
1894 parent=self,
1895 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001896 result.append(subproject)
1897 result.extend(subproject.GetDerivedSubprojects())
1898 return result
1899
1900
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001901# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06001902 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001903 try:
1904 # if revision (sha or tag) is not present then following function
1905 # throws an error.
1906 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1907 return True
1908 except GitError:
1909 # There is no such persistent revision. We have to fetch it.
1910 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001911
Julien Campergue335f5ef2013-10-16 11:02:35 +02001912 def _FetchArchive(self, tarpath, cwd=None):
1913 cmd = ['archive', '-v', '-o', tarpath]
1914 cmd.append('--remote=%s' % self.remote.url)
1915 cmd.append('--prefix=%s/' % self.relpath)
1916 cmd.append(self.revisionExpr)
1917
1918 command = GitCommand(self, cmd, cwd=cwd,
1919 capture_stdout=True,
1920 capture_stderr=True)
1921
1922 if command.Wait() != 0:
1923 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1924
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001925 def _RemoteFetch(self, name=None,
1926 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001927 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001928 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001929 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001930 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001931 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001932 depth=None,
1933 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001934
1935 is_sha1 = False
1936 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001937 # The depth should not be used when fetching to a mirror because
1938 # it will result in a shallow repository that cannot be cloned or
1939 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001940 # The repo project should also never be synced with partial depth.
1941 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1942 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001943
Shawn Pearce69e04d82014-01-29 12:48:54 -08001944 if depth:
1945 current_branch_only = True
1946
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001947 if ID_RE.match(self.revisionExpr) is not None:
1948 is_sha1 = True
1949
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001950 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001951 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001952 # this is a tag and its sha1 value should never change
1953 tag_name = self.revisionExpr[len(R_TAGS):]
1954
1955 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001956 if self._CheckForImmutableRevision():
1957 print('Skipped fetching project %s (already have persistent ref)'
1958 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001959 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001960 if is_sha1 and not depth:
1961 # When syncing a specific commit and --depth is not set:
1962 # * if upstream is explicitly specified and is not a sha1, fetch only
1963 # upstream as users expect only upstream to be fetch.
1964 # Note: The commit might not be in upstream in which case the sync
1965 # will fail.
1966 # * otherwise, fetch all branches to make sure we end up with the
1967 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001968 if self.upstream:
1969 current_branch_only = not ID_RE.match(self.upstream)
1970 else:
1971 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001972
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001973 if not name:
1974 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001975
1976 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001977 remote = self.GetRemote(name)
1978 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001979 ssh_proxy = True
1980
Shawn O. Pearce88443382010-10-08 10:02:09 +02001981 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001982 if alt_dir and 'objects' == os.path.basename(alt_dir):
1983 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001984 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1985 remote = self.GetRemote(name)
1986
David Pursehouse8a68ff92012-09-24 12:15:13 +09001987 all_refs = self.bare_ref.all
1988 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001989 tmp = set()
1990
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301991 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001992 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001993 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001994 all_refs[r] = ref_id
1995 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001996 continue
1997
David Pursehouse8a68ff92012-09-24 12:15:13 +09001998 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001999 continue
2000
David Pursehouse8a68ff92012-09-24 12:15:13 +09002001 r = 'refs/_alt/%s' % ref_id
2002 all_refs[r] = ref_id
2003 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002004 tmp.add(r)
2005
heping3d7bbc92017-04-12 19:51:47 +08002006 tmp_packed_lines = []
2007 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002008
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302009 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002010 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002011 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002012 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002013 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002014
heping3d7bbc92017-04-12 19:51:47 +08002015 tmp_packed = ''.join(tmp_packed_lines)
2016 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002017 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002018 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002019 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002020
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002021 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002022
Conley Owensf97e8382015-01-21 11:12:46 -08002023 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002024 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002025 else:
2026 # If this repo has shallow objects, then we don't know which refs have
2027 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2028 # do this with projects that don't have shallow objects, since it is less
2029 # efficient.
2030 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2031 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002032
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002033 if quiet:
2034 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002035 if not self.worktree:
2036 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002037 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002038
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002039 # If using depth then we should not get all the tags since they may
2040 # be outside of the depth.
2041 if no_tags or depth:
2042 cmd.append('--no-tags')
2043 else:
2044 cmd.append('--tags')
2045
David Pursehouse74cfd272015-10-14 10:50:15 +09002046 if prune:
2047 cmd.append('--prune')
2048
Martin Kellye4e94d22017-03-21 16:05:12 -07002049 if submodules:
2050 cmd.append('--recurse-submodules=on-demand')
2051
Conley Owens80b87fe2014-05-09 17:13:44 -07002052 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002053 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002054 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002055 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002056 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002057 spec.append('tag')
2058 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002059
David Pursehouse403b64e2015-04-27 10:41:33 +09002060 if not self.manifest.IsMirror:
2061 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002062 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002063 # Shallow checkout of a specific commit, fetch from that commit and not
2064 # the heads only as the commit might be deeper in the history.
2065 spec.append(branch)
2066 else:
2067 if is_sha1:
2068 branch = self.upstream
2069 if branch is not None and branch.strip():
2070 if not branch.startswith('refs/'):
2071 branch = R_HEADS + branch
2072 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002073 cmd.extend(spec)
2074
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002075 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002076 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002077 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002078 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002079 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002080 ok = True
2081 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002082 # If needed, run the 'git remote prune' the first time through the loop
2083 elif (not _i and
2084 "error:" in gitcmd.stderr and
2085 "git remote prune" in gitcmd.stderr):
2086 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002087 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002088 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002089 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002090 break
2091 continue
Brian Harring14a66742012-09-28 20:21:57 -07002092 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002093 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2094 # in sha1 mode, we just tried sync'ing from the upstream field; it
2095 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002096 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002097 elif ret < 0:
2098 # Git died with a signal, exit immediately
2099 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002100 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002101
2102 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002103 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002104 if old_packed != '':
2105 _lwrite(packed_refs, old_packed)
2106 else:
2107 os.remove(packed_refs)
2108 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002109
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002110 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002111 # We just synced the upstream given branch; verify we
2112 # got what we wanted, else trigger a second run of all
2113 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002114 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002115 if current_branch_only and depth:
2116 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002117 return self._RemoteFetch(name=name,
2118 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002119 initial=False, quiet=quiet, alt_dir=alt_dir,
2120 depth=None)
2121 else:
2122 # Avoid infinite recursion: sync all branches with depth set to None
2123 return self._RemoteFetch(name=name, current_branch_only=False,
2124 initial=False, quiet=quiet, alt_dir=alt_dir,
2125 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002126
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002127 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002129 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002130 if initial and \
2131 (self.manifest.manifestProject.config.GetString('repo.depth') or
2132 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002133 return False
2134
2135 remote = self.GetRemote(self.remote.name)
2136 bundle_url = remote.url + '/clone.bundle'
2137 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002138 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2139 'persistent-http',
2140 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002141 return False
2142
2143 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2144 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2145
2146 exist_dst = os.path.exists(bundle_dst)
2147 exist_tmp = os.path.exists(bundle_tmp)
2148
2149 if not initial and not exist_dst and not exist_tmp:
2150 return False
2151
2152 if not exist_dst:
2153 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2154 if not exist_dst:
2155 return False
2156
2157 cmd = ['fetch']
2158 if quiet:
2159 cmd.append('--quiet')
2160 if not self.worktree:
2161 cmd.append('--update-head-ok')
2162 cmd.append(bundle_dst)
2163 for f in remote.fetch:
2164 cmd.append(str(f))
2165 cmd.append('refs/tags/*:refs/tags/*')
2166
2167 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002168 if os.path.exists(bundle_dst):
2169 os.remove(bundle_dst)
2170 if os.path.exists(bundle_tmp):
2171 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002172 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002173
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002174 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002175 if os.path.exists(dstPath):
2176 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002177
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002178 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002179 if quiet:
2180 cmd += ['--silent']
2181 if os.path.exists(tmpPath):
2182 size = os.stat(tmpPath).st_size
2183 if size >= 1024:
2184 cmd += ['--continue-at', '%d' % (size,)]
2185 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002186 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002187 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2188 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002189 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002190 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002191 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002192 if srcUrl.startswith('persistent-'):
2193 srcUrl = srcUrl[len('persistent-'):]
2194 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002195
Dave Borowitz137d0132015-01-02 11:12:54 -08002196 if IsTrace():
2197 Trace('%s', ' '.join(cmd))
2198 try:
2199 proc = subprocess.Popen(cmd)
2200 except OSError:
2201 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002202
Dave Borowitz137d0132015-01-02 11:12:54 -08002203 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002204
Dave Borowitz137d0132015-01-02 11:12:54 -08002205 if curlret == 22:
2206 # From curl man page:
2207 # 22: HTTP page not retrieved. The requested url was not found or
2208 # returned another error with the HTTP error code being 400 or above.
2209 # This return code only appears if -f, --fail is used.
2210 if not quiet:
2211 print("Server does not provide clone.bundle; ignoring.",
2212 file=sys.stderr)
2213 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002214
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002215 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002216 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002217 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002218 return True
2219 else:
2220 os.remove(tmpPath)
2221 return False
2222 else:
2223 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002224
Kris Giesingc8d882a2014-12-23 13:02:32 -08002225 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002226 try:
2227 with open(path) as f:
2228 if f.read(16) == '# v2 git bundle\n':
2229 return True
2230 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002231 if not quiet:
2232 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002233 return False
2234 except OSError:
2235 return False
2236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002237 def _Checkout(self, rev, quiet=False):
2238 cmd = ['checkout']
2239 if quiet:
2240 cmd.append('-q')
2241 cmd.append(rev)
2242 cmd.append('--')
2243 if GitCommand(self, cmd).Wait() != 0:
2244 if self._allrefs:
2245 raise GitError('%s checkout %s ' % (self.name, rev))
2246
Anthony King7bdac712014-07-16 12:56:40 +01002247 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002248 cmd = ['cherry-pick']
2249 cmd.append(rev)
2250 cmd.append('--')
2251 if GitCommand(self, cmd).Wait() != 0:
2252 if self._allrefs:
2253 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2254
Anthony King7bdac712014-07-16 12:56:40 +01002255 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002256 cmd = ['revert']
2257 cmd.append('--no-edit')
2258 cmd.append(rev)
2259 cmd.append('--')
2260 if GitCommand(self, cmd).Wait() != 0:
2261 if self._allrefs:
2262 raise GitError('%s revert %s ' % (self.name, rev))
2263
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002264 def _ResetHard(self, rev, quiet=True):
2265 cmd = ['reset', '--hard']
2266 if quiet:
2267 cmd.append('-q')
2268 cmd.append(rev)
2269 if GitCommand(self, cmd).Wait() != 0:
2270 raise GitError('%s reset --hard %s ' % (self.name, rev))
2271
Martin Kellye4e94d22017-03-21 16:05:12 -07002272 def _SyncSubmodules(self, quiet=True):
2273 cmd = ['submodule', 'update', '--init', '--recursive']
2274 if quiet:
2275 cmd.append('-q')
2276 if GitCommand(self, cmd).Wait() != 0:
2277 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2278
Anthony King7bdac712014-07-16 12:56:40 +01002279 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002280 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002281 if onto is not None:
2282 cmd.extend(['--onto', onto])
2283 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002284 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002285 raise GitError('%s rebase %s ' % (self.name, upstream))
2286
Pierre Tardy3d125942012-05-04 12:18:12 +02002287 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002289 if ffonly:
2290 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002291 if GitCommand(self, cmd).Wait() != 0:
2292 raise GitError('%s merge %s ' % (self.name, head))
2293
Kevin Degiabaa7f32014-11-12 11:27:45 -07002294 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002295 init_git_dir = not os.path.exists(self.gitdir)
2296 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002297 try:
2298 # Initialize the bare repository, which contains all of the objects.
2299 if init_obj_dir:
2300 os.makedirs(self.objdir)
2301 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002302
Kevin Degib1a07b82015-07-27 13:33:43 -06002303 # If we have a separate directory to hold refs, initialize it as well.
2304 if self.objdir != self.gitdir:
2305 if init_git_dir:
2306 os.makedirs(self.gitdir)
2307
2308 if init_obj_dir or init_git_dir:
2309 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2310 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002311 try:
2312 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2313 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002314 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002315 print("Retrying clone after deleting %s" %
2316 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002317 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002318 platform_utils.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002319 if self.worktree and os.path.exists(os.path.realpath
2320 (self.worktree)):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002321 platform_utils.rmtree(os.path.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002322 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2323 except:
2324 raise e
2325 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002326
Kevin Degi384b3c52014-10-16 16:02:58 -06002327 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002328 mp = self.manifest.manifestProject
2329 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002330
Kevin Degib1a07b82015-07-27 13:33:43 -06002331 if ref_dir or mirror_git:
2332 if not mirror_git:
2333 mirror_git = os.path.join(ref_dir, self.name + '.git')
2334 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2335 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002336
Kevin Degib1a07b82015-07-27 13:33:43 -06002337 if os.path.exists(mirror_git):
2338 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002339
Kevin Degib1a07b82015-07-27 13:33:43 -06002340 elif os.path.exists(repo_git):
2341 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002342
Kevin Degib1a07b82015-07-27 13:33:43 -06002343 else:
2344 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002345
Kevin Degib1a07b82015-07-27 13:33:43 -06002346 if ref_dir:
2347 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2348 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002349
Kevin Degib1a07b82015-07-27 13:33:43 -06002350 self._UpdateHooks()
2351
2352 m = self.manifest.manifestProject.config
2353 for key in ['user.name', 'user.email']:
2354 if m.Has(key, include_defaults=False):
2355 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002356 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002357 if self.manifest.IsMirror:
2358 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002359 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002360 self.config.SetString('core.bare', None)
2361 except Exception:
2362 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002363 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002364 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002365 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002366 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002367
Jimmie Westera0444582012-10-24 13:44:42 +02002368 def _UpdateHooks(self):
2369 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002370 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002372 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002373 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002374 if not os.path.exists(hooks):
2375 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002376 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002377 name = os.path.basename(stock_hook)
2378
Victor Boivie65e0f352011-04-18 11:23:29 +02002379 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002380 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002381 # Don't install a Gerrit Code Review hook if this
2382 # project does not appear to use it for reviews.
2383 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002384 # Since the manifest project is one of those, but also
2385 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002386 continue
2387
2388 dst = os.path.join(hooks, name)
2389 if os.path.islink(dst):
2390 continue
2391 if os.path.exists(dst):
2392 if filecmp.cmp(stock_hook, dst, shallow=False):
2393 os.remove(dst)
2394 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002395 _warn("%s: Not replacing locally modified %s hook",
2396 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002397 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002398 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002399 platform_utils.symlink(
2400 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002401 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002402 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002403 raise GitError('filesystem must support symlinks')
2404 else:
2405 raise
2406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002408 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002409 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002410 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002411 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002412 remote.review = self.remote.review
2413 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002414
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002415 if self.worktree:
2416 remote.ResetFetch(mirror=False)
2417 else:
2418 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002419 remote.Save()
2420
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002421 def _InitMRef(self):
2422 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002423 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002424
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002425 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002426 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002427
2428 def _InitAnyMRef(self, ref):
2429 cur = self.bare_ref.symref(ref)
2430
2431 if self.revisionId:
2432 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2433 msg = 'manifest set to %s' % self.revisionId
2434 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002435 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002436 else:
2437 remote = self.GetRemote(self.remote.name)
2438 dst = remote.ToLocal(self.revisionExpr)
2439 if cur != dst:
2440 msg = 'manifest set to %s' % self.revisionExpr
2441 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002442
Kevin Degi384b3c52014-10-16 16:02:58 -06002443 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002444 symlink_files = self.shareable_files[:]
2445 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002446 if share_refs:
2447 symlink_files += self.working_tree_files
2448 symlink_dirs += self.working_tree_dirs
2449 to_symlink = symlink_files + symlink_dirs
2450 for name in set(to_symlink):
2451 dst = os.path.realpath(os.path.join(destdir, name))
2452 if os.path.lexists(dst):
2453 src = os.path.realpath(os.path.join(srcdir, name))
2454 # Fail if the links are pointing to the wrong place
2455 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002456 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002457 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002458 'work tree. If you\'re comfortable with the '
2459 'possibility of losing the work tree\'s git metadata,'
2460 ' use `repo sync --force-sync {0}` to '
2461 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002462
David James8d201162013-10-11 17:03:19 -07002463 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2464 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2465
2466 Args:
2467 gitdir: The bare git repository. Must already be initialized.
2468 dotgit: The repository you would like to initialize.
2469 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2470 Only one work tree can store refs under a given |gitdir|.
2471 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2472 This saves you the effort of initializing |dotgit| yourself.
2473 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002474 symlink_files = self.shareable_files[:]
2475 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002476 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002477 symlink_files += self.working_tree_files
2478 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002479 to_symlink = symlink_files + symlink_dirs
2480
2481 to_copy = []
2482 if copy_all:
2483 to_copy = os.listdir(gitdir)
2484
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002485 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002486 for name in set(to_copy).union(to_symlink):
2487 try:
2488 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002489 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002490
Kevin Degi384b3c52014-10-16 16:02:58 -06002491 if os.path.lexists(dst):
2492 continue
David James8d201162013-10-11 17:03:19 -07002493
2494 # If the source dir doesn't exist, create an empty dir.
2495 if name in symlink_dirs and not os.path.lexists(src):
2496 os.makedirs(src)
2497
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002498 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002499 platform_utils.symlink(
2500 os.path.relpath(src, os.path.dirname(dst)), dst)
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002501 elif copy_all and not os.path.islink(dst):
2502 if os.path.isdir(src):
2503 shutil.copytree(src, dst)
2504 elif os.path.isfile(src):
2505 shutil.copy(src, dst)
2506
Conley Owens80b87fe2014-05-09 17:13:44 -07002507 # If the source file doesn't exist, ensure the destination
2508 # file doesn't either.
2509 if name in symlink_files and not os.path.lexists(src):
2510 try:
2511 os.remove(dst)
2512 except OSError:
2513 pass
2514
David James8d201162013-10-11 17:03:19 -07002515 except OSError as e:
2516 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002517 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002518 else:
2519 raise
2520
Martin Kellye4e94d22017-03-21 16:05:12 -07002521 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002522 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002523 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002524 try:
2525 if init_dotgit:
2526 os.makedirs(dotgit)
2527 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2528 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002529
Kevin Degiabaa7f32014-11-12 11:27:45 -07002530 try:
2531 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2532 except GitError as e:
2533 if force_sync:
2534 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002535 platform_utils.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002536 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002537 except:
2538 raise e
2539 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002540
Kevin Degib1a07b82015-07-27 13:33:43 -06002541 if init_dotgit:
2542 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002543
Kevin Degib1a07b82015-07-27 13:33:43 -06002544 cmd = ['read-tree', '--reset', '-u']
2545 cmd.append('-v')
2546 cmd.append(HEAD)
2547 if GitCommand(self, cmd).Wait() != 0:
2548 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002549
Martin Kellye4e94d22017-03-21 16:05:12 -07002550 if submodules:
2551 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002552 self._CopyAndLinkFiles()
2553 except Exception:
2554 if init_dotgit:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002555 platform_utils.rmtree(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002556 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002557
2558 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002559 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002560
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002561 def _revlist(self, *args, **kw):
2562 a = []
2563 a.extend(args)
2564 a.append('--')
2565 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566
2567 @property
2568 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002569 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002570
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002571 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002572 """Get logs between two revisions of this project."""
2573 comp = '..'
2574 if rev1:
2575 revs = [rev1]
2576 if rev2:
2577 revs.extend([comp, rev2])
2578 cmd = ['log', ''.join(revs)]
2579 out = DiffColoring(self.config)
2580 if out.is_on and color:
2581 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002582 if pretty_format is not None:
2583 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002584 if oneline:
2585 cmd.append('--oneline')
2586
2587 try:
2588 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2589 if log.Wait() == 0:
2590 return log.stdout
2591 except GitError:
2592 # worktree may not exist if groups changed for example. In that case,
2593 # try in gitdir instead.
2594 if not os.path.exists(self.worktree):
2595 return self.bare_git.log(*cmd[1:])
2596 else:
2597 raise
2598 return None
2599
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002600 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2601 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002602 """Get the list of logs from this revision to given revisionId"""
2603 logs = {}
2604 selfId = self.GetRevisionId(self._allrefs)
2605 toId = toProject.GetRevisionId(toProject._allrefs)
2606
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002607 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2608 pretty_format=pretty_format)
2609 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2610 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002611 return logs
2612
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002613 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002614
David James8d201162013-10-11 17:03:19 -07002615 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 self._project = project
2617 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002618 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002619
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002620 def LsOthers(self):
2621 p = GitCommand(self._project,
2622 ['ls-files',
2623 '-z',
2624 '--others',
2625 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002626 bare=False,
David James8d201162013-10-11 17:03:19 -07002627 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002628 capture_stdout=True,
2629 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630 if p.Wait() == 0:
2631 out = p.stdout
2632 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002633 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002634 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002635 return []
2636
2637 def DiffZ(self, name, *args):
2638 cmd = [name]
2639 cmd.append('-z')
2640 cmd.extend(args)
2641 p = GitCommand(self._project,
2642 cmd,
David James8d201162013-10-11 17:03:19 -07002643 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002644 bare=False,
2645 capture_stdout=True,
2646 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 try:
2648 out = p.process.stdout.read()
2649 r = {}
2650 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002651 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002653 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002654 info = next(out)
2655 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002656 except StopIteration:
2657 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002658
2659 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002660
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 def __init__(self, path, omode, nmode, oid, nid, state):
2662 self.path = path
2663 self.src_path = None
2664 self.old_mode = omode
2665 self.new_mode = nmode
2666 self.old_id = oid
2667 self.new_id = nid
2668
2669 if len(state) == 1:
2670 self.status = state
2671 self.level = None
2672 else:
2673 self.status = state[:1]
2674 self.level = state[1:]
2675 while self.level.startswith('0'):
2676 self.level = self.level[1:]
2677
2678 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002679 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002680 if info.status in ('R', 'C'):
2681 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002682 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002683 r[info.path] = info
2684 return r
2685 finally:
2686 p.Wait()
2687
2688 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002689 if self._bare:
2690 path = os.path.join(self._project.gitdir, HEAD)
2691 else:
2692 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002693 try:
2694 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002695 except IOError as e:
2696 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002697 try:
2698 line = fd.read()
2699 finally:
2700 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302701 try:
2702 line = line.decode()
2703 except AttributeError:
2704 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002705 if line.startswith('ref: '):
2706 return line[5:-1]
2707 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002708
2709 def SetHead(self, ref, message=None):
2710 cmdv = []
2711 if message is not None:
2712 cmdv.extend(['-m', message])
2713 cmdv.append(HEAD)
2714 cmdv.append(ref)
2715 self.symbolic_ref(*cmdv)
2716
2717 def DetachHead(self, new, message=None):
2718 cmdv = ['--no-deref']
2719 if message is not None:
2720 cmdv.extend(['-m', message])
2721 cmdv.append(HEAD)
2722 cmdv.append(new)
2723 self.update_ref(*cmdv)
2724
2725 def UpdateRef(self, name, new, old=None,
2726 message=None,
2727 detach=False):
2728 cmdv = []
2729 if message is not None:
2730 cmdv.extend(['-m', message])
2731 if detach:
2732 cmdv.append('--no-deref')
2733 cmdv.append(name)
2734 cmdv.append(new)
2735 if old is not None:
2736 cmdv.append(old)
2737 self.update_ref(*cmdv)
2738
2739 def DeleteRef(self, name, old=None):
2740 if not old:
2741 old = self.rev_parse(name)
2742 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002743 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002744
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002745 def rev_list(self, *args, **kw):
2746 if 'format' in kw:
2747 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2748 else:
2749 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750 cmdv.extend(args)
2751 p = GitCommand(self._project,
2752 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002753 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002754 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002755 capture_stdout=True,
2756 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002757 r = []
2758 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002759 if line[-1] == '\n':
2760 line = line[:-1]
2761 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002762 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002763 raise GitError('%s rev-list %s: %s' %
2764 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765 return r
2766
2767 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002768 """Allow arbitrary git commands using pythonic syntax.
2769
2770 This allows you to do things like:
2771 git_obj.rev_parse('HEAD')
2772
2773 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2774 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002775 Any other positional arguments will be passed to the git command, and the
2776 following keyword arguments are supported:
2777 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002778
2779 Args:
2780 name: The name of the git command to call. Any '_' characters will
2781 be replaced with '-'.
2782
2783 Returns:
2784 A callable object that will try to call git with the named command.
2785 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002786 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002787
Dave Borowitz091f8932012-10-23 17:01:04 -07002788 def runner(*args, **kwargs):
2789 cmdv = []
2790 config = kwargs.pop('config', None)
2791 for k in kwargs:
2792 raise TypeError('%s() got an unexpected keyword argument %r'
2793 % (name, k))
2794 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002795 if not git_require((1, 7, 2)):
2796 raise ValueError('cannot set config on command line for %s()'
2797 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302798 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002799 cmdv.append('-c')
2800 cmdv.append('%s=%s' % (k, v))
2801 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002802 cmdv.extend(args)
2803 p = GitCommand(self._project,
2804 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002805 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002806 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002807 capture_stdout=True,
2808 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002810 raise GitError('%s %s: %s' %
2811 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302813 try:
Conley Owensedd01512013-09-26 12:59:58 -07002814 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302815 except AttributeError:
2816 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002817 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2818 return r[:-1]
2819 return r
2820 return runner
2821
2822
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002823class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002824
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002825 def __str__(self):
2826 return 'prior sync failed; rebase still in progress'
2827
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002828
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002829class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002830
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002831 def __str__(self):
2832 return 'contains uncommitted changes'
2833
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002834
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002835class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002836
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002837 def __init__(self, project, text):
2838 self.project = project
2839 self.text = text
2840
2841 def Print(self, syncbuf):
2842 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2843 syncbuf.out.nl()
2844
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002845
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002846class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002847
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002848 def __init__(self, project, why):
2849 self.project = project
2850 self.why = why
2851
2852 def Print(self, syncbuf):
2853 syncbuf.out.fail('error: %s/: %s',
2854 self.project.relpath,
2855 str(self.why))
2856 syncbuf.out.nl()
2857
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002858
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002859class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002860
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002861 def __init__(self, project, action):
2862 self.project = project
2863 self.action = action
2864
2865 def Run(self, syncbuf):
2866 out = syncbuf.out
2867 out.project('project %s/', self.project.relpath)
2868 out.nl()
2869 try:
2870 self.action()
2871 out.nl()
2872 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002873 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002874 out.nl()
2875 return False
2876
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002877
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002878class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002879
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002880 def __init__(self, config):
2881 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002882 self.project = self.printer('header', attr='bold')
2883 self.info = self.printer('info')
2884 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002885
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002886
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002887class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002888
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002889 def __init__(self, config, detach_head=False):
2890 self._messages = []
2891 self._failures = []
2892 self._later_queue1 = []
2893 self._later_queue2 = []
2894
2895 self.out = _SyncColoring(config)
2896 self.out.redirect(sys.stderr)
2897
2898 self.detach_head = detach_head
2899 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002900 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002901
2902 def info(self, project, fmt, *args):
2903 self._messages.append(_InfoMessage(project, fmt % args))
2904
2905 def fail(self, project, err=None):
2906 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002907 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002908
2909 def later1(self, project, what):
2910 self._later_queue1.append(_Later(project, what))
2911
2912 def later2(self, project, what):
2913 self._later_queue2.append(_Later(project, what))
2914
2915 def Finish(self):
2916 self._PrintMessages()
2917 self._RunLater()
2918 self._PrintMessages()
2919 return self.clean
2920
David Rileye0684ad2017-04-05 00:02:59 -07002921 def Recently(self):
2922 recent_clean = self.recent_clean
2923 self.recent_clean = True
2924 return recent_clean
2925
2926 def _MarkUnclean(self):
2927 self.clean = False
2928 self.recent_clean = False
2929
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002930 def _RunLater(self):
2931 for q in ['_later_queue1', '_later_queue2']:
2932 if not self._RunQueue(q):
2933 return
2934
2935 def _RunQueue(self, queue):
2936 for m in getattr(self, queue):
2937 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07002938 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002939 return False
2940 setattr(self, queue, [])
2941 return True
2942
2943 def _PrintMessages(self):
2944 for m in self._messages:
2945 m.Print(self)
2946 for m in self._failures:
2947 m.Print(self)
2948
2949 self._messages = []
2950 self._failures = []
2951
2952
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002953class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002954
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002955 """A special project housed under .repo.
2956 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002957
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002958 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002959 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002960 manifest=manifest,
2961 name=name,
2962 gitdir=gitdir,
2963 objdir=gitdir,
2964 worktree=worktree,
2965 remote=RemoteSpec('origin'),
2966 relpath='.repo/%s' % name,
2967 revisionExpr='refs/heads/master',
2968 revisionId=None,
2969 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002970
2971 def PreSync(self):
2972 if self.Exists:
2973 cb = self.CurrentBranch
2974 if cb:
2975 base = self.GetBranch(cb).merge
2976 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002977 self.revisionExpr = base
2978 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002979
Martin Kelly224a31a2017-07-10 14:46:25 -07002980 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02002981 """ Prepare MetaProject for manifest branch switch
2982 """
2983
2984 # detach and delete manifest branch, allowing a new
2985 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002986 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07002987 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02002988 syncbuf.Finish()
2989
2990 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002991 ['update-ref', '-d', 'refs/heads/default'],
2992 capture_stdout=True,
2993 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002994
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002996 def LastFetch(self):
2997 try:
2998 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2999 return os.path.getmtime(fh)
3000 except OSError:
3001 return 0
3002
3003 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003004 def HasChanges(self):
3005 """Has the remote received new commits not yet checked out?
3006 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003007 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003008 return False
3009
David Pursehouse8a68ff92012-09-24 12:15:13 +09003010 all_refs = self.bare_ref.all
3011 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003012 head = self.work_git.GetHead()
3013 if head.startswith(R_HEADS):
3014 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003015 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003016 except KeyError:
3017 head = None
3018
3019 if revid == head:
3020 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003021 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003022 return True
3023 return False