blob: 0b7baeed498caaec08e658b5f499c8c829a9af74 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040043if is_python3():
44 import urllib.parse
45else:
46 import imp
47 import urlparse
48 urllib = imp.new_module('urllib')
49 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090050 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053051 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090052 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053053
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070054
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055def _lwrite(path, content):
56 lock = '%s.lock' % path
57
Chirayu Desai303a82f2014-08-19 22:57:17 +053058 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 try:
60 fd.write(content)
61 finally:
62 fd.close()
63
64 try:
65 os.rename(lock, path)
66 except OSError:
67 os.remove(lock)
68 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
Jonathan Nieder93719792015-03-17 11:29:58 -070088_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070089
90
Jonathan Nieder93719792015-03-17 11:29:58 -070091def _ProjectHooks():
92 """List the hooks present in the 'hooks' directory.
93
94 These hooks are project hooks and are copied to the '.git/hooks' directory
95 of all subprojects.
96
97 This function caches the list of hooks (based on the contents of the
98 'repo/hooks' directory) on the first call.
99
100 Returns:
101 A list of absolute paths to all of the files in the hooks directory.
102 """
103 global _project_hook_list
104 if _project_hook_list is None:
105 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
106 d = os.path.join(d, 'hooks')
107 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
108 return _project_hook_list
109
110
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700111class DownloadedChange(object):
112 _commit_cache = None
113
114 def __init__(self, project, base, change_id, ps_id, commit):
115 self.project = project
116 self.base = base
117 self.change_id = change_id
118 self.ps_id = ps_id
119 self.commit = commit
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700124 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700150 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
151 '--abbrev-commit',
152 '--pretty=oneline',
153 '--reverse',
154 '--date-order',
155 not_rev(self.base),
156 R_HEADS + self.name,
157 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700176 def UploadForReview(self, people,
177 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000178 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200179 private=False,
180 wip=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700181 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800182 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700183 people,
Brian Harring435370c2012-07-28 15:37:04 -0700184 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000185 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200186 private=private,
187 wip=wip,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400188 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700190 def GetPublishedRefs(self):
191 refs = {}
192 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700193 self.branch.remote.SshReviewUrl(self.project.UserEmail),
194 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700195 for line in output.split('\n'):
196 try:
197 (sha, ref) = line.split()
198 refs[sha] = ref
199 except ValueError:
200 pass
201
202 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700204
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700206
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 def __init__(self, config):
208 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100209 self.project = self.printer('header', attr='bold')
210 self.branch = self.printer('header', attr='bold')
211 self.nobranch = self.printer('nobranch', fg='red')
212 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
Anthony King7bdac712014-07-16 12:56:40 +0100214 self.added = self.printer('added', fg='green')
215 self.changed = self.printer('changed', fg='red')
216 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218
219class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 def __init__(self, config):
222 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100223 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700225
Anthony King7bdac712014-07-16 12:56:40 +0100226class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227
James W. Mills24c13082012-04-12 15:04:13 -0500228 def __init__(self, name, value, keep):
229 self.name = name
230 self.value = value
231 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
Anthony King7bdac712014-07-16 12:56:40 +0100234class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800236 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 self.src = src
238 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 self.abs_src = abssrc
240 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
242 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800243 src = self.abs_src
244 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 # copy file if it does not exist or is out of date
246 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
247 try:
248 # remove existing file first, since it might be read-only
249 if os.path.exists(dest):
250 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400251 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200252 dest_dir = os.path.dirname(dest)
253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 shutil.copy(src, dest)
256 # make the file read-only
257 mode = os.stat(dest)[stat.ST_MODE]
258 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
259 os.chmod(dest, mode)
260 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700261 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700263
Anthony King7bdac712014-07-16 12:56:40 +0100264class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700265
Wink Saville4c426ef2015-06-03 08:05:17 -0700266 def __init__(self, git_worktree, src, dest, relsrc, absdest):
267 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500268 self.src = src
269 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700270 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 self.abs_dest = absdest
272
Wink Saville4c426ef2015-06-03 08:05:17 -0700273 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500274 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700275 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 try:
277 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800278 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700279 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500280 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700281 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500282 if not os.path.isdir(dest_dir):
283 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700284 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500285 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700286 _error('Cannot link file %s to %s', relSrc, absDest)
287
288 def _Link(self):
289 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
290 on the src linking all of the files in the source in to the destination
291 directory.
292 """
293 # We use the absSrc to handle the situation where the current directory
294 # is not the root of the repo
295 absSrc = os.path.join(self.git_worktree, self.src)
296 if os.path.exists(absSrc):
297 # Entity exists so just a simple one to one link operation
298 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
299 else:
300 # Entity doesn't exist assume there is a wild card
301 absDestDir = self.abs_dest
302 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
303 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700304 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700305 else:
306 absSrcFiles = glob.glob(absSrc)
307 for absSrcFile in absSrcFiles:
308 # Create a releative path from source dir to destination dir
309 absSrcDir = os.path.dirname(absSrcFile)
310 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
311
312 # Get the source file name
313 srcFile = os.path.basename(absSrcFile)
314
315 # Now form the final full paths to srcFile. They will be
316 # absolute for the desintaiton and relative for the srouce.
317 absDest = os.path.join(absDestDir, srcFile)
318 relSrc = os.path.join(relSrcDir, srcFile)
319 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500320
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700321
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700322class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700323
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700324 def __init__(self,
325 name,
Anthony King7bdac712014-07-16 12:56:40 +0100326 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700327 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100328 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700329 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700330 orig_name=None,
331 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700332 self.name = name
333 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700334 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700335 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100336 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700337 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700338 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700340
Doug Anderson37282b42011-03-04 11:54:18 -0800341class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700342
Doug Anderson37282b42011-03-04 11:54:18 -0800343 """A RepoHook contains information about a script to run as a hook.
344
345 Hooks are used to run a python script before running an upload (for instance,
346 to run presubmit checks). Eventually, we may have hooks for other actions.
347
348 This shouldn't be confused with files in the 'repo/hooks' directory. Those
349 files are copied into each '.git/hooks' folder for each project. Repo-level
350 hooks are associated instead with repo actions.
351
352 Hooks are always python. When a hook is run, we will load the hook into the
353 interpreter and execute its main() function.
354 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700355
Doug Anderson37282b42011-03-04 11:54:18 -0800356 def __init__(self,
357 hook_type,
358 hooks_project,
359 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400360 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800361 abort_if_user_denies=False):
362 """RepoHook constructor.
363
364 Params:
365 hook_type: A string representing the type of hook. This is also used
366 to figure out the name of the file containing the hook. For
367 example: 'pre-upload'.
368 hooks_project: The project containing the repo hooks. If you have a
369 manifest, this is manifest.repo_hooks_project. OK if this is None,
370 which will make the hook a no-op.
371 topdir: Repo's top directory (the one containing the .repo directory).
372 Scripts will run with CWD as this directory. If you have a manifest,
373 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400374 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800375 abort_if_user_denies: If True, we'll throw a HookError() if the user
376 doesn't allow us to run the hook.
377 """
378 self._hook_type = hook_type
379 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400380 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800381 self._topdir = topdir
382 self._abort_if_user_denies = abort_if_user_denies
383
384 # Store the full path to the script for convenience.
385 if self._hooks_project:
386 self._script_fullpath = os.path.join(self._hooks_project.worktree,
387 self._hook_type + '.py')
388 else:
389 self._script_fullpath = None
390
391 def _GetHash(self):
392 """Return a hash of the contents of the hooks directory.
393
394 We'll just use git to do this. This hash has the property that if anything
395 changes in the directory we will return a different has.
396
397 SECURITY CONSIDERATION:
398 This hash only represents the contents of files in the hook directory, not
399 any other files imported or called by hooks. Changes to imported files
400 can change the script behavior without affecting the hash.
401
402 Returns:
403 A string representing the hash. This will always be ASCII so that it can
404 be printed to the user easily.
405 """
406 assert self._hooks_project, "Must have hooks to calculate their hash."
407
408 # We will use the work_git object rather than just calling GetRevisionId().
409 # That gives us a hash of the latest checked in version of the files that
410 # the user will actually be executing. Specifically, GetRevisionId()
411 # doesn't appear to change even if a user checks out a different version
412 # of the hooks repo (via git checkout) nor if a user commits their own revs.
413 #
414 # NOTE: Local (non-committed) changes will not be factored into this hash.
415 # I think this is OK, since we're really only worried about warning the user
416 # about upstream changes.
417 return self._hooks_project.work_git.rev_parse('HEAD')
418
419 def _GetMustVerb(self):
420 """Return 'must' if the hook is required; 'should' if not."""
421 if self._abort_if_user_denies:
422 return 'must'
423 else:
424 return 'should'
425
426 def _CheckForHookApproval(self):
427 """Check to see whether this hook has been approved.
428
Mike Frysinger40252c22016-08-15 21:23:44 -0400429 We'll accept approval of manifest URLs if they're using secure transports.
430 This way the user can say they trust the manifest hoster. For insecure
431 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800432
433 Note that we ask permission for each individual hook even though we use
434 the hash of all hooks when detecting changes. We'd like the user to be
435 able to approve / deny each hook individually. We only use the hash of all
436 hooks because there is no other easy way to detect changes to local imports.
437
438 Returns:
439 True if this hook is approved to run; False otherwise.
440
441 Raises:
442 HookError: Raised if the user doesn't approve and abort_if_user_denies
443 was passed to the consturctor.
444 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400445 if self._ManifestUrlHasSecureScheme():
446 return self._CheckForHookApprovalManifest()
447 else:
448 return self._CheckForHookApprovalHash()
449
450 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
451 changed_prompt):
452 """Check for approval for a particular attribute and hook.
453
454 Args:
455 subkey: The git config key under [repo.hooks.<hook_type>] to store the
456 last approved string.
457 new_val: The new value to compare against the last approved one.
458 main_prompt: Message to display to the user to ask for approval.
459 changed_prompt: Message explaining why we're re-asking for approval.
460
461 Returns:
462 True if this hook is approved to run; False otherwise.
463
464 Raises:
465 HookError: Raised if the user doesn't approve and abort_if_user_denies
466 was passed to the consturctor.
467 """
Doug Anderson37282b42011-03-04 11:54:18 -0800468 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400469 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800470
Mike Frysinger40252c22016-08-15 21:23:44 -0400471 # Get the last value that the user approved for this hook; may be None.
472 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800473
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800475 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400476 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800477 # Approval matched. We're done.
478 return True
479 else:
480 # Give the user a reason why we're prompting, since they last told
481 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400482 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800483 else:
484 prompt = ''
485
486 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
487 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400488 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530489 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900490 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800491
492 # User is doing a one-time approval.
493 if response in ('y', 'yes'):
494 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400495 elif response == 'always':
496 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800497 return True
498
499 # For anything else, we'll assume no approval.
500 if self._abort_if_user_denies:
501 raise HookError('You must allow the %s hook or use --no-verify.' %
502 self._hook_type)
503
504 return False
505
Mike Frysinger40252c22016-08-15 21:23:44 -0400506 def _ManifestUrlHasSecureScheme(self):
507 """Check if the URI for the manifest is a secure transport."""
508 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
509 parse_results = urllib.parse.urlparse(self._manifest_url)
510 return parse_results.scheme in secure_schemes
511
512 def _CheckForHookApprovalManifest(self):
513 """Check whether the user has approved this manifest host.
514
515 Returns:
516 True if this hook is approved to run; False otherwise.
517 """
518 return self._CheckForHookApprovalHelper(
519 'approvedmanifest',
520 self._manifest_url,
521 'Run hook scripts from %s' % (self._manifest_url,),
522 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
523
524 def _CheckForHookApprovalHash(self):
525 """Check whether the user has approved the hooks repo.
526
527 Returns:
528 True if this hook is approved to run; False otherwise.
529 """
530 prompt = ('Repo %s run the script:\n'
531 ' %s\n'
532 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700533 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400534 return self._CheckForHookApprovalHelper(
535 'approvedhash',
536 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700537 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400538 'Scripts have changed since %s was allowed.' % (self._hook_type,))
539
Doug Anderson37282b42011-03-04 11:54:18 -0800540 def _ExecuteHook(self, **kwargs):
541 """Actually execute the given hook.
542
543 This will run the hook's 'main' function in our python interpreter.
544
545 Args:
546 kwargs: Keyword arguments to pass to the hook. These are often specific
547 to the hook type. For instance, pre-upload hooks will contain
548 a project_list.
549 """
550 # Keep sys.path and CWD stashed away so that we can always restore them
551 # upon function exit.
552 orig_path = os.getcwd()
553 orig_syspath = sys.path
554
555 try:
556 # Always run hooks with CWD as topdir.
557 os.chdir(self._topdir)
558
559 # Put the hook dir as the first item of sys.path so hooks can do
560 # relative imports. We want to replace the repo dir as [0] so
561 # hooks can't import repo files.
562 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
563
564 # Exec, storing global context in the context dict. We catch exceptions
565 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500566 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800567 try:
Anthony King70f68902014-05-05 21:15:34 +0100568 exec(compile(open(self._script_fullpath).read(),
569 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800570 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700571 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
572 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800573
574 # Running the script should have defined a main() function.
575 if 'main' not in context:
576 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
577
Doug Anderson37282b42011-03-04 11:54:18 -0800578 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
579 # We don't actually want hooks to define their main with this argument--
580 # it's there to remind them that their hook should always take **kwargs.
581 # For instance, a pre-upload hook should be defined like:
582 # def main(project_list, **kwargs):
583 #
584 # This allows us to later expand the API without breaking old hooks.
585 kwargs = kwargs.copy()
586 kwargs['hook_should_take_kwargs'] = True
587
588 # Call the main function in the hook. If the hook should cause the
589 # build to fail, it will raise an Exception. We'll catch that convert
590 # to a HookError w/ just the failing traceback.
591 try:
592 context['main'](**kwargs)
593 except Exception:
594 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700595 'above.' % (traceback.format_exc(),
596 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800597 finally:
598 # Restore sys.path and CWD.
599 sys.path = orig_syspath
600 os.chdir(orig_path)
601
602 def Run(self, user_allows_all_hooks, **kwargs):
603 """Run the hook.
604
605 If the hook doesn't exist (because there is no hooks project or because
606 this particular hook is not enabled), this is a no-op.
607
608 Args:
609 user_allows_all_hooks: If True, we will never prompt about running the
610 hook--we'll just assume it's OK to run it.
611 kwargs: Keyword arguments to pass to the hook. These are often specific
612 to the hook type. For instance, pre-upload hooks will contain
613 a project_list.
614
615 Raises:
616 HookError: If there was a problem finding the hook or the user declined
617 to run a required hook (from _CheckForHookApproval).
618 """
619 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700620 if ((not self._hooks_project) or (self._hook_type not in
621 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800622 return
623
624 # Bail with a nice error if we can't find the hook.
625 if not os.path.isfile(self._script_fullpath):
626 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
627
628 # Make sure the user is OK with running the hook.
629 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
630 return
631
632 # Run the hook with the same version of python we're using.
633 self._ExecuteHook(**kwargs)
634
635
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600637 # These objects can be shared between several working trees.
638 shareable_files = ['description', 'info']
639 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
640 # These objects can only be used by a single working tree.
641 working_tree_files = ['config', 'packed-refs', 'shallow']
642 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700643
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644 def __init__(self,
645 manifest,
646 name,
647 remote,
648 gitdir,
David James8d201162013-10-11 17:03:19 -0700649 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650 worktree,
651 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700652 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800653 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100654 rebase=True,
655 groups=None,
656 sync_c=False,
657 sync_s=False,
658 clone_depth=None,
659 upstream=None,
660 parent=None,
661 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900662 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700663 optimized_fetch=False,
664 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800665 """Init a Project object.
666
667 Args:
668 manifest: The XmlManifest object.
669 name: The `name` attribute of manifest.xml's project element.
670 remote: RemoteSpec object specifying its remote's properties.
671 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700672 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800673 worktree: Absolute path of git working tree.
674 relpath: Relative path of git working tree to repo's top directory.
675 revisionExpr: The `revision` attribute of manifest.xml's project element.
676 revisionId: git commit id for checking out.
677 rebase: The `rebase` attribute of manifest.xml's project element.
678 groups: The `groups` attribute of manifest.xml's project element.
679 sync_c: The `sync-c` attribute of manifest.xml's project element.
680 sync_s: The `sync-s` attribute of manifest.xml's project element.
681 upstream: The `upstream` attribute of manifest.xml's project element.
682 parent: The parent Project object.
683 is_derived: False if the project was explicitly defined in the manifest;
684 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400685 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900686 optimized_fetch: If True, when a project is set to a sha1 revision, only
687 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700688 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800689 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 self.manifest = manifest
691 self.name = name
692 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800693 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700694 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800695 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700696 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800697 else:
698 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700700 self.revisionExpr = revisionExpr
701
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700702 if revisionId is None \
703 and revisionExpr \
704 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700705 self.revisionId = revisionExpr
706 else:
707 self.revisionId = revisionId
708
Mike Pontillod3153822012-02-28 11:53:24 -0800709 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700710 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700711 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800712 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900713 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700714 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800715 self.parent = parent
716 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900717 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800718 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500722 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500723 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700724 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
725 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800727 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700728 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800729 else:
730 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700731 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700732 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700733 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400734 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700735 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
Doug Anderson37282b42011-03-04 11:54:18 -0800737 # This will be filled in if a project is later identified to be the
738 # project containing repo hooks.
739 self.enabled_repo_hooks = []
740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800742 def Derived(self):
743 return self.is_derived
744
745 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600747 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749 @property
750 def CurrentBranch(self):
751 """Obtain the name of the currently checked out branch.
752 The branch name omits the 'refs/heads/' prefix.
753 None is returned if the project is on a detached HEAD.
754 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700755 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756 if b.startswith(R_HEADS):
757 return b[len(R_HEADS):]
758 return None
759
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700760 def IsRebaseInProgress(self):
761 w = self.worktree
762 g = os.path.join(w, '.git')
763 return os.path.exists(os.path.join(g, 'rebase-apply')) \
764 or os.path.exists(os.path.join(g, 'rebase-merge')) \
765 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 def IsDirty(self, consider_untracked=True):
768 """Is the working directory modified in some way?
769 """
770 self.work_git.update_index('-q',
771 '--unmerged',
772 '--ignore-missing',
773 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900774 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775 return True
776 if self.work_git.DiffZ('diff-files'):
777 return True
778 if consider_untracked and self.work_git.LsOthers():
779 return True
780 return False
781
782 _userident_name = None
783 _userident_email = None
784
785 @property
786 def UserName(self):
787 """Obtain the user's personal name.
788 """
789 if self._userident_name is None:
790 self._LoadUserIdentity()
791 return self._userident_name
792
793 @property
794 def UserEmail(self):
795 """Obtain the user's email address. This is very likely
796 to be their Gerrit login.
797 """
798 if self._userident_email is None:
799 self._LoadUserIdentity()
800 return self._userident_email
801
802 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900803 u = self.bare_git.var('GIT_COMMITTER_IDENT')
804 m = re.compile("^(.*) <([^>]*)> ").match(u)
805 if m:
806 self._userident_name = m.group(1)
807 self._userident_email = m.group(2)
808 else:
809 self._userident_name = ''
810 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
812 def GetRemote(self, name):
813 """Get the configuration for a single remote.
814 """
815 return self.config.GetRemote(name)
816
817 def GetBranch(self, name):
818 """Get the configuration for a single branch.
819 """
820 return self.config.GetBranch(name)
821
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700822 def GetBranches(self):
823 """Get all existing local branches.
824 """
825 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900826 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700827 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700828
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530829 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700830 if name.startswith(R_HEADS):
831 name = name[len(R_HEADS):]
832 b = self.GetBranch(name)
833 b.current = name == current
834 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900835 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700836 heads[name] = b
837
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530838 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700839 if name.startswith(R_PUB):
840 name = name[len(R_PUB):]
841 b = heads.get(name)
842 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900843 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700844
845 return heads
846
Colin Cross5acde752012-03-28 20:15:45 -0700847 def MatchesGroups(self, manifest_groups):
848 """Returns true if the manifest groups specified at init should cause
849 this project to be synced.
850 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700851 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700852
853 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700854 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700855 manifest_groups: "-group1,group2"
856 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500857
858 The special manifest group "default" will match any project that
859 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700860 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500861 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700862 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700863 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500864 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700865
Conley Owens971de8e2012-04-16 10:36:08 -0700866 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700867 for group in expanded_manifest_groups:
868 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700869 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700870 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700871 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700872
Conley Owens971de8e2012-04-16 10:36:08 -0700873 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700875# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700876 def UncommitedFiles(self, get_all=True):
877 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700879 Args:
880 get_all: a boolean, if True - get information about all different
881 uncommitted files. If False - return as soon as any kind of
882 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500883 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700884 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500885 self.work_git.update_index('-q',
886 '--unmerged',
887 '--ignore-missing',
888 '--refresh')
889 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700890 details.append("rebase in progress")
891 if not get_all:
892 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500893
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700894 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
895 if changes:
896 details.extend(changes)
897 if not get_all:
898 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500899
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700900 changes = self.work_git.DiffZ('diff-files').keys()
901 if changes:
902 details.extend(changes)
903 if not get_all:
904 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500905
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700906 changes = self.work_git.LsOthers()
907 if changes:
908 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500909
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700910 return details
911
912 def HasChanges(self):
913 """Returns true if there are uncommitted changes.
914 """
915 if self.UncommitedFiles(get_all=False):
916 return True
917 else:
918 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500919
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600920 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200922
923 Args:
924 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600925 quiet: If True then only print the project name. Do not print
926 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 """
928 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700929 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200930 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700931 print(file=output_redir)
932 print('project %s/' % self.relpath, file=output_redir)
933 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 return
935
936 self.work_git.update_index('-q',
937 '--unmerged',
938 '--ignore-missing',
939 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700940 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
942 df = self.work_git.DiffZ('diff-files')
943 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100944 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700945 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946
947 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700948 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200949 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700950 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600952 if quiet:
953 out.nl()
954 return 'DIRTY'
955
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956 branch = self.CurrentBranch
957 if branch is None:
958 out.nobranch('(*** NO BRANCH ***)')
959 else:
960 out.branch('branch %s', branch)
961 out.nl()
962
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700963 if rb:
964 out.important('prior sync failed; rebase still in progress')
965 out.nl()
966
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 paths = list()
968 paths.extend(di.keys())
969 paths.extend(df.keys())
970 paths.extend(do)
971
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530972 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900973 try:
974 i = di[p]
975 except KeyError:
976 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900978 try:
979 f = df[p]
980 except KeyError:
981 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200982
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900983 if i:
984 i_status = i.status.upper()
985 else:
986 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900988 if f:
989 f_status = f.status.lower()
990 else:
991 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992
993 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800994 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700995 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996 else:
997 line = ' %s%s\t%s' % (i_status, f_status, p)
998
999 if i and not f:
1000 out.added('%s', line)
1001 elif (i and f) or (not i and f):
1002 out.changed('%s', line)
1003 elif not i and not f:
1004 out.untracked('%s', line)
1005 else:
1006 out.write('%s', line)
1007 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001008
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001009 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010
pelyad67872d2012-03-28 14:49:58 +03001011 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012 """Prints the status of the repository to stdout.
1013 """
1014 out = DiffColoring(self.config)
1015 cmd = ['diff']
1016 if out.is_on:
1017 cmd.append('--color')
1018 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001019 if absolute_paths:
1020 cmd.append('--src-prefix=a/%s/' % self.relpath)
1021 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 cmd.append('--')
1023 p = GitCommand(self,
1024 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001025 capture_stdout=True,
1026 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 has_diff = False
1028 for line in p.process.stdout:
1029 if not has_diff:
1030 out.nl()
1031 out.project('project %s/' % self.relpath)
1032 out.nl()
1033 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001034 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 p.Wait()
1036
1037
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001038# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039
David Pursehouse8a68ff92012-09-24 12:15:13 +09001040 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 """Was the branch published (uploaded) for code review?
1042 If so, returns the SHA-1 hash of the last published
1043 state for the branch.
1044 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001046 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001047 try:
1048 return self.bare_git.rev_parse(key)
1049 except GitError:
1050 return None
1051 else:
1052 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001053 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001054 except KeyError:
1055 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056
David Pursehouse8a68ff92012-09-24 12:15:13 +09001057 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 """Prunes any stale published refs.
1059 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001060 if all_refs is None:
1061 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 heads = set()
1063 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301064 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 if name.startswith(R_HEADS):
1066 heads.add(name)
1067 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001068 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301070 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 n = name[len(R_PUB):]
1072 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001073 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001075 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 """List any branches which can be uploaded for review.
1077 """
1078 heads = {}
1079 pubed = {}
1080
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301081 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001083 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001085 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
1087 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301088 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001089 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001091 if selected_branch and branch != selected_branch:
1092 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001094 rb = self.GetUploadableBranch(branch)
1095 if rb:
1096 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 return ready
1098
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001099 def GetUploadableBranch(self, branch_name):
1100 """Get a single uploadable branch, or None.
1101 """
1102 branch = self.GetBranch(branch_name)
1103 base = branch.LocalMerge
1104 if branch.LocalMerge:
1105 rb = ReviewableBranch(self, branch, base)
1106 if rb.commits:
1107 return rb
1108 return None
1109
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001110 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001111 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001112 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001113 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001114 private=False,
1115 wip=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001116 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 """Uploads the named branch for code review.
1118 """
1119 if branch is None:
1120 branch = self.CurrentBranch
1121 if branch is None:
1122 raise GitError('not currently on a branch')
1123
1124 branch = self.GetBranch(branch)
1125 if not branch.LocalMerge:
1126 raise GitError('branch %s does not track a remote' % branch.name)
1127 if not branch.remote.review:
1128 raise GitError('remote %s has no review url' % branch.remote.name)
1129
Bryan Jacobsf609f912013-05-06 13:36:24 -04001130 if dest_branch is None:
1131 dest_branch = self.dest_branch
1132 if dest_branch is None:
1133 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 if not dest_branch.startswith(R_HEADS):
1135 dest_branch = R_HEADS + dest_branch
1136
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001137 if not branch.remote.projectname:
1138 branch.remote.projectname = self.name
1139 branch.remote.Save()
1140
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001141 url = branch.remote.ReviewUrl(self.UserEmail)
1142 if url is None:
1143 raise UploadError('review not configured')
1144 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001145
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001146 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001147 rp = ['gerrit receive-pack']
1148 for e in people[0]:
1149 rp.append('--reviewer=%s' % sq(e))
1150 for e in people[1]:
1151 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001152 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001153
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001154 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001155
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001156 if dest_branch.startswith(R_HEADS):
1157 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001158
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001159 upload_type = 'for'
1160 if draft:
1161 upload_type = 'drafts'
1162
1163 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1164 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001165 if auto_topic:
1166 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao87984c62017-08-02 16:55:03 +02001167
Shawn Pearce45d21682013-02-28 00:35:51 -08001168 if not url.startswith('ssh://'):
1169 rp = ['r=%s' % p for p in people[0]] + \
1170 ['cc=%s' % p for p in people[1]]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001171 if private:
1172 rp = rp + ['private']
1173 if wip:
1174 rp = rp + ['wip']
Shawn Pearce45d21682013-02-28 00:35:51 -08001175 if rp:
1176 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001177 cmd.append(ref_spec)
1178
Anthony King7bdac712014-07-16 12:56:40 +01001179 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001180 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
1182 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1183 self.bare_git.UpdateRef(R_PUB + branch.name,
1184 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001185 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186
1187
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001188# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
Julien Campergue335f5ef2013-10-16 11:02:35 +02001190 def _ExtractArchive(self, tarpath, path=None):
1191 """Extract the given tar on its current location
1192
1193 Args:
1194 - tarpath: The path to the actual tar file
1195
1196 """
1197 try:
1198 with tarfile.open(tarpath, 'r') as tar:
1199 tar.extractall(path=path)
1200 return True
1201 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001202 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001203 return False
1204
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001205 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001206 quiet=False,
1207 is_new=None,
1208 current_branch_only=False,
1209 force_sync=False,
1210 clone_bundle=True,
1211 no_tags=False,
1212 archive=False,
1213 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001214 prune=False,
1215 submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001216 """Perform only the network IO portion of the sync process.
1217 Local working directory/branch state is not affected.
1218 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001219 if archive and not isinstance(self, MetaProject):
1220 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001221 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001222 return False
1223
1224 name = self.relpath.replace('\\', '/')
1225 name = name.replace('/', '_')
1226 tarpath = '%s.tar' % name
1227 topdir = self.manifest.topdir
1228
1229 try:
1230 self._FetchArchive(tarpath, cwd=topdir)
1231 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001232 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001233 return False
1234
1235 # From now on, we only need absolute tarpath
1236 tarpath = os.path.join(topdir, tarpath)
1237
1238 if not self._ExtractArchive(tarpath, path=topdir):
1239 return False
1240 try:
1241 os.remove(tarpath)
1242 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001243 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001244 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001245 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001246 if is_new is None:
1247 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001248 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001249 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001250 else:
1251 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001253
1254 if is_new:
1255 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1256 try:
1257 fd = open(alt, 'rb')
1258 try:
1259 alt_dir = fd.readline().rstrip()
1260 finally:
1261 fd.close()
1262 except IOError:
1263 alt_dir = None
1264 else:
1265 alt_dir = None
1266
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001267 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001268 and alt_dir is None \
1269 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001270 is_new = False
1271
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001272 if not current_branch_only:
1273 if self.sync_c:
1274 current_branch_only = True
1275 elif not self.manifest._loaded:
1276 # Manifest cannot check defaults until it syncs.
1277 current_branch_only = False
1278 elif self.manifest.default.sync_c:
1279 current_branch_only = True
1280
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001281 if self.clone_depth:
1282 depth = self.clone_depth
1283 else:
1284 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1285
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001286 need_to_fetch = not (optimized_fetch and
1287 (ID_RE.match(self.revisionExpr) and
1288 self._CheckForSha1()))
1289 if (need_to_fetch and
1290 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1291 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001292 no_tags=no_tags, prune=prune, depth=depth,
1293 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001294 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001295
1296 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001297 self._InitMRef()
1298 else:
1299 self._InitMirrorHead()
1300 try:
1301 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1302 except OSError:
1303 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001305
1306 def PostRepoUpgrade(self):
1307 self._InitHooks()
1308
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001309 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001310 if self.manifest.isGitcClient:
1311 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001312 for copyfile in self.copyfiles:
1313 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001314 for linkfile in self.linkfiles:
1315 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316
Julien Camperguedd654222014-01-09 16:21:37 +01001317 def GetCommitRevisionId(self):
1318 """Get revisionId of a commit.
1319
1320 Use this method instead of GetRevisionId to get the id of the commit rather
1321 than the id of the current git object (for example, a tag)
1322
1323 """
1324 if not self.revisionExpr.startswith(R_TAGS):
1325 return self.GetRevisionId(self._allrefs)
1326
1327 try:
1328 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1329 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001330 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1331 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001332
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001334 if self.revisionId:
1335 return self.revisionId
1336
1337 rem = self.GetRemote(self.remote.name)
1338 rev = rem.ToLocal(self.revisionExpr)
1339
David Pursehouse8a68ff92012-09-24 12:15:13 +09001340 if all_refs is not None and rev in all_refs:
1341 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001342
1343 try:
1344 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1345 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001346 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1347 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348
Martin Kellye4e94d22017-03-21 16:05:12 -07001349 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350 """Perform only the local IO portion of the sync process.
1351 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001353 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001354 all_refs = self.bare_ref.all
1355 self.CleanPublishedCache(all_refs)
1356 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001357
David Pursehouse1d947b32012-10-25 12:23:11 +09001358 def _doff():
1359 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001360 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001361
Martin Kellye4e94d22017-03-21 16:05:12 -07001362 def _dosubmodules():
1363 self._SyncSubmodules(quiet=True)
1364
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001365 head = self.work_git.GetHead()
1366 if head.startswith(R_HEADS):
1367 branch = head[len(R_HEADS):]
1368 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001369 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001370 except KeyError:
1371 head = None
1372 else:
1373 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001375 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 # Currently on a detached HEAD. The user is assumed to
1377 # not have any local modifications worth worrying about.
1378 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001379 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001380 syncbuf.fail(self, _PriorSyncFailedError())
1381 return
1382
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001383 if head == revid:
1384 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001385 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001386 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001387 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001388 # The copy/linkfile config may have changed.
1389 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001390 return
1391 else:
1392 lost = self._revlist(not_rev(revid), HEAD)
1393 if lost:
1394 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001395
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001397 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001398 if submodules:
1399 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001400 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001401 syncbuf.fail(self, e)
1402 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001403 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001404 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001406 if head == revid:
1407 # No changes; don't do anything further.
1408 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001409 # The copy/linkfile config may have changed.
1410 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001411 return
1412
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001414
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001415 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001416 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001417 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001418 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001419 syncbuf.info(self,
1420 "leaving %s; does not track upstream",
1421 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001423 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001424 if submodules:
1425 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001426 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001427 syncbuf.fail(self, e)
1428 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001429 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001430 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001431
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001432 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001433 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001435 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001436 if not_merged:
1437 if upstream_gain:
1438 # The user has published this branch and some of those
1439 # commits are not yet merged upstream. We do not want
1440 # to rewrite the published commits so we punt.
1441 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001442 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001443 "branch %s is published (but not merged) and is now "
1444 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001445 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001446 elif pub == head:
1447 # All published commits are merged, and thus we are a
1448 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001449 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001450 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001451 if submodules:
1452 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001453 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001454
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001455 # Examine the local commits not in the remote. Find the
1456 # last one attributed to this user, if any.
1457 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001458 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001459 last_mine = None
1460 cnt_mine = 0
1461 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301462 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001463 if committer_email == self.UserEmail:
1464 last_mine = commit_id
1465 cnt_mine += 1
1466
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001467 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001468 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001469
1470 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001471 syncbuf.fail(self, _DirtyError())
1472 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001474 # If the upstream switched on us, warn the user.
1475 #
1476 if branch.merge != self.revisionExpr:
1477 if branch.merge and self.revisionExpr:
1478 syncbuf.info(self,
1479 'manifest switched %s...%s',
1480 branch.merge,
1481 self.revisionExpr)
1482 elif branch.merge:
1483 syncbuf.info(self,
1484 'manifest no longer tracks %s',
1485 branch.merge)
1486
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001487 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001488 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001489 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001491 syncbuf.info(self,
1492 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001493 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001495 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001496 if not ID_RE.match(self.revisionExpr):
1497 # in case of manifest sync the revisionExpr might be a SHA1
1498 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001499 if not branch.merge.startswith('refs/'):
1500 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001501 branch.Save()
1502
Mike Pontillod3153822012-02-28 11:53:24 -08001503 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001504 def _docopyandlink():
1505 self._CopyAndLinkFiles()
1506
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001507 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001508 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001509 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001510 if submodules:
1511 syncbuf.later2(self, _dosubmodules)
1512 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001513 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001515 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001516 if submodules:
1517 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001518 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001519 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001520 syncbuf.fail(self, e)
1521 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001523 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001524 if submodules:
1525 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001527 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528 # dest should already be an absolute path, but src is project relative
1529 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001530 abssrc = os.path.join(self.worktree, src)
1531 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001533 def AddLinkFile(self, src, dest, absdest):
1534 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001535 # make src relative path to dest
1536 absdestdir = os.path.dirname(absdest)
1537 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001538 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001539
James W. Mills24c13082012-04-12 15:04:13 -05001540 def AddAnnotation(self, name, value, keep):
1541 self.annotations.append(_Annotation(name, value, keep))
1542
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001543 def DownloadPatchSet(self, change_id, patch_id):
1544 """Download a single patch set of a single change to FETCH_HEAD.
1545 """
1546 remote = self.GetRemote(self.remote.name)
1547
1548 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001549 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001550 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001551 if GitCommand(self, cmd, bare=True).Wait() != 0:
1552 return None
1553 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001554 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001555 change_id,
1556 patch_id,
1557 self.bare_git.rev_parse('FETCH_HEAD'))
1558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001559
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001560# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561
Simran Basib9a1b732015-08-20 12:19:28 -07001562 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563 """Create a new branch off the manifest's revision.
1564 """
Simran Basib9a1b732015-08-20 12:19:28 -07001565 if not branch_merge:
1566 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001567 head = self.work_git.GetHead()
1568 if head == (R_HEADS + name):
1569 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570
David Pursehouse8a68ff92012-09-24 12:15:13 +09001571 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001572 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001573 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001574 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001575 capture_stdout=True,
1576 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001577
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001578 branch = self.GetBranch(name)
1579 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001580 branch.merge = branch_merge
1581 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1582 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001583 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001584
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001585 if head.startswith(R_HEADS):
1586 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001587 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001588 except KeyError:
1589 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001590 if revid and head and revid == head:
1591 ref = os.path.join(self.gitdir, R_HEADS + name)
1592 try:
1593 os.makedirs(os.path.dirname(ref))
1594 except OSError:
1595 pass
1596 _lwrite(ref, '%s\n' % revid)
1597 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1598 'ref: %s%s\n' % (R_HEADS, name))
1599 branch.Save()
1600 return True
1601
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001602 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001603 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001604 capture_stdout=True,
1605 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001606 branch.Save()
1607 return True
1608 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609
Wink Saville02d79452009-04-10 13:01:24 -07001610 def CheckoutBranch(self, name):
1611 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001612
1613 Args:
1614 name: The name of the branch to checkout.
1615
1616 Returns:
1617 True if the checkout succeeded; False if it didn't; None if the branch
1618 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001619 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001620 rev = R_HEADS + name
1621 head = self.work_git.GetHead()
1622 if head == rev:
1623 # Already on the branch
1624 #
1625 return True
Wink Saville02d79452009-04-10 13:01:24 -07001626
David Pursehouse8a68ff92012-09-24 12:15:13 +09001627 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001628 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001629 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001630 except KeyError:
1631 # Branch does not exist in this project
1632 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001633 return None
Wink Saville02d79452009-04-10 13:01:24 -07001634
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001635 if head.startswith(R_HEADS):
1636 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001637 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001638 except KeyError:
1639 head = None
1640
1641 if head == revid:
1642 # Same revision; just update HEAD to point to the new
1643 # target branch, but otherwise take no other action.
1644 #
1645 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1646 'ref: %s%s\n' % (R_HEADS, name))
1647 return True
1648
1649 return GitCommand(self,
1650 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001651 capture_stdout=True,
1652 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001653
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001654 def AbandonBranch(self, name):
1655 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001656
1657 Args:
1658 name: The name of the branch to abandon.
1659
1660 Returns:
1661 True if the abandon succeeded; False if it didn't; None if the branch
1662 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001663 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001664 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001665 all_refs = self.bare_ref.all
1666 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001667 # Doesn't exist
1668 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001669
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001670 head = self.work_git.GetHead()
1671 if head == rev:
1672 # We can't destroy the branch while we are sitting
1673 # on it. Switch to a detached HEAD.
1674 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001675 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001676
David Pursehouse8a68ff92012-09-24 12:15:13 +09001677 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001678 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001679 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1680 '%s\n' % revid)
1681 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001682 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001683
1684 return GitCommand(self,
1685 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001686 capture_stdout=True,
1687 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001688
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001689 def PruneHeads(self):
1690 """Prune any topic branches already merged into upstream.
1691 """
1692 cb = self.CurrentBranch
1693 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001694 left = self._allrefs
1695 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696 if name.startswith(R_HEADS):
1697 name = name[len(R_HEADS):]
1698 if cb is None or name != cb:
1699 kill.append(name)
1700
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001701 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001702 if cb is not None \
1703 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001704 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 self.work_git.DetachHead(HEAD)
1706 kill.append(cb)
1707
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001709 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001711 try:
1712 self.bare_git.DetachHead(rev)
1713
1714 b = ['branch', '-d']
1715 b.extend(kill)
1716 b = GitCommand(self, b, bare=True,
1717 capture_stdout=True,
1718 capture_stderr=True)
1719 b.Wait()
1720 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001721 if ID_RE.match(old):
1722 self.bare_git.DetachHead(old)
1723 else:
1724 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001725 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001727 for branch in kill:
1728 if (R_HEADS + branch) not in left:
1729 self.CleanPublishedCache()
1730 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731
1732 if cb and cb not in kill:
1733 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001734 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735
1736 kept = []
1737 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001738 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 branch = self.GetBranch(branch)
1740 base = branch.LocalMerge
1741 if not base:
1742 base = rev
1743 kept.append(ReviewableBranch(self, branch, base))
1744 return kept
1745
1746
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001747# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001748
1749 def GetRegisteredSubprojects(self):
1750 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001751
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001752 def rec(subprojects):
1753 if not subprojects:
1754 return
1755 result.extend(subprojects)
1756 for p in subprojects:
1757 rec(p.subprojects)
1758 rec(self.subprojects)
1759 return result
1760
1761 def _GetSubmodules(self):
1762 # Unfortunately we cannot call `git submodule status --recursive` here
1763 # because the working tree might not exist yet, and it cannot be used
1764 # without a working tree in its current implementation.
1765
1766 def get_submodules(gitdir, rev):
1767 # Parse .gitmodules for submodule sub_paths and sub_urls
1768 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1769 if not sub_paths:
1770 return []
1771 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1772 # revision of submodule repository
1773 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1774 submodules = []
1775 for sub_path, sub_url in zip(sub_paths, sub_urls):
1776 try:
1777 sub_rev = sub_revs[sub_path]
1778 except KeyError:
1779 # Ignore non-exist submodules
1780 continue
1781 submodules.append((sub_rev, sub_path, sub_url))
1782 return submodules
1783
1784 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1785 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001786
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001787 def parse_gitmodules(gitdir, rev):
1788 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1789 try:
Anthony King7bdac712014-07-16 12:56:40 +01001790 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1791 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001792 except GitError:
1793 return [], []
1794 if p.Wait() != 0:
1795 return [], []
1796
1797 gitmodules_lines = []
1798 fd, temp_gitmodules_path = tempfile.mkstemp()
1799 try:
1800 os.write(fd, p.stdout)
1801 os.close(fd)
1802 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001803 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1804 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001805 if p.Wait() != 0:
1806 return [], []
1807 gitmodules_lines = p.stdout.split('\n')
1808 except GitError:
1809 return [], []
1810 finally:
1811 os.remove(temp_gitmodules_path)
1812
1813 names = set()
1814 paths = {}
1815 urls = {}
1816 for line in gitmodules_lines:
1817 if not line:
1818 continue
1819 m = re_path.match(line)
1820 if m:
1821 names.add(m.group(1))
1822 paths[m.group(1)] = m.group(2)
1823 continue
1824 m = re_url.match(line)
1825 if m:
1826 names.add(m.group(1))
1827 urls[m.group(1)] = m.group(2)
1828 continue
1829 names = sorted(names)
1830 return ([paths.get(name, '') for name in names],
1831 [urls.get(name, '') for name in names])
1832
1833 def git_ls_tree(gitdir, rev, paths):
1834 cmd = ['ls-tree', rev, '--']
1835 cmd.extend(paths)
1836 try:
Anthony King7bdac712014-07-16 12:56:40 +01001837 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1838 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001839 except GitError:
1840 return []
1841 if p.Wait() != 0:
1842 return []
1843 objects = {}
1844 for line in p.stdout.split('\n'):
1845 if not line.strip():
1846 continue
1847 object_rev, object_path = line.split()[2:4]
1848 objects[object_path] = object_rev
1849 return objects
1850
1851 try:
1852 rev = self.GetRevisionId()
1853 except GitError:
1854 return []
1855 return get_submodules(self.gitdir, rev)
1856
1857 def GetDerivedSubprojects(self):
1858 result = []
1859 if not self.Exists:
1860 # If git repo does not exist yet, querying its submodules will
1861 # mess up its states; so return here.
1862 return result
1863 for rev, path, url in self._GetSubmodules():
1864 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001865 relpath, worktree, gitdir, objdir = \
1866 self.manifest.GetSubprojectPaths(self, name, path)
1867 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001868 if project:
1869 result.extend(project.GetDerivedSubprojects())
1870 continue
David James8d201162013-10-11 17:03:19 -07001871
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001872 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001873 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001874 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001875 review=self.remote.review,
1876 revision=self.remote.revision)
1877 subproject = Project(manifest=self.manifest,
1878 name=name,
1879 remote=remote,
1880 gitdir=gitdir,
1881 objdir=objdir,
1882 worktree=worktree,
1883 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001884 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001885 revisionId=rev,
1886 rebase=self.rebase,
1887 groups=self.groups,
1888 sync_c=self.sync_c,
1889 sync_s=self.sync_s,
1890 parent=self,
1891 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001892 result.append(subproject)
1893 result.extend(subproject.GetDerivedSubprojects())
1894 return result
1895
1896
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001897# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001898 def _CheckForSha1(self):
1899 try:
1900 # if revision (sha or tag) is not present then following function
1901 # throws an error.
1902 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1903 return True
1904 except GitError:
1905 # There is no such persistent revision. We have to fetch it.
1906 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001907
Julien Campergue335f5ef2013-10-16 11:02:35 +02001908 def _FetchArchive(self, tarpath, cwd=None):
1909 cmd = ['archive', '-v', '-o', tarpath]
1910 cmd.append('--remote=%s' % self.remote.url)
1911 cmd.append('--prefix=%s/' % self.relpath)
1912 cmd.append(self.revisionExpr)
1913
1914 command = GitCommand(self, cmd, cwd=cwd,
1915 capture_stdout=True,
1916 capture_stderr=True)
1917
1918 if command.Wait() != 0:
1919 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1920
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001921 def _RemoteFetch(self, name=None,
1922 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001923 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001924 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001925 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001926 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001927 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001928 depth=None,
1929 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001930
1931 is_sha1 = False
1932 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001933 # The depth should not be used when fetching to a mirror because
1934 # it will result in a shallow repository that cannot be cloned or
1935 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001936 # The repo project should also never be synced with partial depth.
1937 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1938 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001939
Shawn Pearce69e04d82014-01-29 12:48:54 -08001940 if depth:
1941 current_branch_only = True
1942
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001943 if ID_RE.match(self.revisionExpr) is not None:
1944 is_sha1 = True
1945
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001946 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001947 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001948 # this is a tag and its sha1 value should never change
1949 tag_name = self.revisionExpr[len(R_TAGS):]
1950
1951 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001952 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001953 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001954 if is_sha1 and not depth:
1955 # When syncing a specific commit and --depth is not set:
1956 # * if upstream is explicitly specified and is not a sha1, fetch only
1957 # upstream as users expect only upstream to be fetch.
1958 # Note: The commit might not be in upstream in which case the sync
1959 # will fail.
1960 # * otherwise, fetch all branches to make sure we end up with the
1961 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001962 if self.upstream:
1963 current_branch_only = not ID_RE.match(self.upstream)
1964 else:
1965 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001966
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001967 if not name:
1968 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001969
1970 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001971 remote = self.GetRemote(name)
1972 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001973 ssh_proxy = True
1974
Shawn O. Pearce88443382010-10-08 10:02:09 +02001975 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001976 if alt_dir and 'objects' == os.path.basename(alt_dir):
1977 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001978 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1979 remote = self.GetRemote(name)
1980
David Pursehouse8a68ff92012-09-24 12:15:13 +09001981 all_refs = self.bare_ref.all
1982 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001983 tmp = set()
1984
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301985 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001986 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001987 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001988 all_refs[r] = ref_id
1989 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001990 continue
1991
David Pursehouse8a68ff92012-09-24 12:15:13 +09001992 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001993 continue
1994
David Pursehouse8a68ff92012-09-24 12:15:13 +09001995 r = 'refs/_alt/%s' % ref_id
1996 all_refs[r] = ref_id
1997 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001998 tmp.add(r)
1999
heping3d7bbc92017-04-12 19:51:47 +08002000 tmp_packed_lines = []
2001 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002002
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302003 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002004 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002005 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002006 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002007 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002008
heping3d7bbc92017-04-12 19:51:47 +08002009 tmp_packed = ''.join(tmp_packed_lines)
2010 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002011 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002012 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002013 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002014
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002015 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002016
Conley Owensf97e8382015-01-21 11:12:46 -08002017 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002018 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002019 else:
2020 # If this repo has shallow objects, then we don't know which refs have
2021 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2022 # do this with projects that don't have shallow objects, since it is less
2023 # efficient.
2024 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2025 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002026
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002027 if quiet:
2028 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002029 if not self.worktree:
2030 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002031 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002032
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002033 # If using depth then we should not get all the tags since they may
2034 # be outside of the depth.
2035 if no_tags or depth:
2036 cmd.append('--no-tags')
2037 else:
2038 cmd.append('--tags')
2039
David Pursehouse74cfd272015-10-14 10:50:15 +09002040 if prune:
2041 cmd.append('--prune')
2042
Martin Kellye4e94d22017-03-21 16:05:12 -07002043 if submodules:
2044 cmd.append('--recurse-submodules=on-demand')
2045
Conley Owens80b87fe2014-05-09 17:13:44 -07002046 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002047 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002048 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002049 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002050 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002051 spec.append('tag')
2052 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002053
David Pursehouse403b64e2015-04-27 10:41:33 +09002054 if not self.manifest.IsMirror:
2055 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002056 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002057 # Shallow checkout of a specific commit, fetch from that commit and not
2058 # the heads only as the commit might be deeper in the history.
2059 spec.append(branch)
2060 else:
2061 if is_sha1:
2062 branch = self.upstream
2063 if branch is not None and branch.strip():
2064 if not branch.startswith('refs/'):
2065 branch = R_HEADS + branch
2066 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002067 cmd.extend(spec)
2068
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002069 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002070 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002071 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002072 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002073 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002074 ok = True
2075 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002076 # If needed, run the 'git remote prune' the first time through the loop
2077 elif (not _i and
2078 "error:" in gitcmd.stderr and
2079 "git remote prune" in gitcmd.stderr):
2080 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002081 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002082 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002083 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002084 break
2085 continue
Brian Harring14a66742012-09-28 20:21:57 -07002086 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002087 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2088 # in sha1 mode, we just tried sync'ing from the upstream field; it
2089 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002090 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002091 elif ret < 0:
2092 # Git died with a signal, exit immediately
2093 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002094 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002095
2096 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002097 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002098 if old_packed != '':
2099 _lwrite(packed_refs, old_packed)
2100 else:
2101 os.remove(packed_refs)
2102 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002103
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002104 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002105 # We just synced the upstream given branch; verify we
2106 # got what we wanted, else trigger a second run of all
2107 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002108 if not self._CheckForSha1():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002109 if current_branch_only and depth:
2110 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002111 return self._RemoteFetch(name=name,
2112 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002113 initial=False, quiet=quiet, alt_dir=alt_dir,
2114 depth=None)
2115 else:
2116 # Avoid infinite recursion: sync all branches with depth set to None
2117 return self._RemoteFetch(name=name, current_branch_only=False,
2118 initial=False, quiet=quiet, alt_dir=alt_dir,
2119 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002120
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002121 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002122
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002123 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002124 if initial and \
2125 (self.manifest.manifestProject.config.GetString('repo.depth') or
2126 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002127 return False
2128
2129 remote = self.GetRemote(self.remote.name)
2130 bundle_url = remote.url + '/clone.bundle'
2131 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002132 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2133 'persistent-http',
2134 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002135 return False
2136
2137 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2138 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2139
2140 exist_dst = os.path.exists(bundle_dst)
2141 exist_tmp = os.path.exists(bundle_tmp)
2142
2143 if not initial and not exist_dst and not exist_tmp:
2144 return False
2145
2146 if not exist_dst:
2147 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2148 if not exist_dst:
2149 return False
2150
2151 cmd = ['fetch']
2152 if quiet:
2153 cmd.append('--quiet')
2154 if not self.worktree:
2155 cmd.append('--update-head-ok')
2156 cmd.append(bundle_dst)
2157 for f in remote.fetch:
2158 cmd.append(str(f))
2159 cmd.append('refs/tags/*:refs/tags/*')
2160
2161 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002162 if os.path.exists(bundle_dst):
2163 os.remove(bundle_dst)
2164 if os.path.exists(bundle_tmp):
2165 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002166 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002167
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002168 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002169 if os.path.exists(dstPath):
2170 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002171
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002172 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002173 if quiet:
2174 cmd += ['--silent']
2175 if os.path.exists(tmpPath):
2176 size = os.stat(tmpPath).st_size
2177 if size >= 1024:
2178 cmd += ['--continue-at', '%d' % (size,)]
2179 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002180 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002181 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2182 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002183 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002184 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002185 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002186 if srcUrl.startswith('persistent-'):
2187 srcUrl = srcUrl[len('persistent-'):]
2188 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002189
Dave Borowitz137d0132015-01-02 11:12:54 -08002190 if IsTrace():
2191 Trace('%s', ' '.join(cmd))
2192 try:
2193 proc = subprocess.Popen(cmd)
2194 except OSError:
2195 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002196
Dave Borowitz137d0132015-01-02 11:12:54 -08002197 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002198
Dave Borowitz137d0132015-01-02 11:12:54 -08002199 if curlret == 22:
2200 # From curl man page:
2201 # 22: HTTP page not retrieved. The requested url was not found or
2202 # returned another error with the HTTP error code being 400 or above.
2203 # This return code only appears if -f, --fail is used.
2204 if not quiet:
2205 print("Server does not provide clone.bundle; ignoring.",
2206 file=sys.stderr)
2207 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002208
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002209 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002210 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002211 os.rename(tmpPath, dstPath)
2212 return True
2213 else:
2214 os.remove(tmpPath)
2215 return False
2216 else:
2217 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002218
Kris Giesingc8d882a2014-12-23 13:02:32 -08002219 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002220 try:
2221 with open(path) as f:
2222 if f.read(16) == '# v2 git bundle\n':
2223 return True
2224 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002225 if not quiet:
2226 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002227 return False
2228 except OSError:
2229 return False
2230
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002231 def _Checkout(self, rev, quiet=False):
2232 cmd = ['checkout']
2233 if quiet:
2234 cmd.append('-q')
2235 cmd.append(rev)
2236 cmd.append('--')
2237 if GitCommand(self, cmd).Wait() != 0:
2238 if self._allrefs:
2239 raise GitError('%s checkout %s ' % (self.name, rev))
2240
Anthony King7bdac712014-07-16 12:56:40 +01002241 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002242 cmd = ['cherry-pick']
2243 cmd.append(rev)
2244 cmd.append('--')
2245 if GitCommand(self, cmd).Wait() != 0:
2246 if self._allrefs:
2247 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2248
Anthony King7bdac712014-07-16 12:56:40 +01002249 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002250 cmd = ['revert']
2251 cmd.append('--no-edit')
2252 cmd.append(rev)
2253 cmd.append('--')
2254 if GitCommand(self, cmd).Wait() != 0:
2255 if self._allrefs:
2256 raise GitError('%s revert %s ' % (self.name, rev))
2257
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002258 def _ResetHard(self, rev, quiet=True):
2259 cmd = ['reset', '--hard']
2260 if quiet:
2261 cmd.append('-q')
2262 cmd.append(rev)
2263 if GitCommand(self, cmd).Wait() != 0:
2264 raise GitError('%s reset --hard %s ' % (self.name, rev))
2265
Martin Kellye4e94d22017-03-21 16:05:12 -07002266 def _SyncSubmodules(self, quiet=True):
2267 cmd = ['submodule', 'update', '--init', '--recursive']
2268 if quiet:
2269 cmd.append('-q')
2270 if GitCommand(self, cmd).Wait() != 0:
2271 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2272
Anthony King7bdac712014-07-16 12:56:40 +01002273 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002274 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002275 if onto is not None:
2276 cmd.extend(['--onto', onto])
2277 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002278 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002279 raise GitError('%s rebase %s ' % (self.name, upstream))
2280
Pierre Tardy3d125942012-05-04 12:18:12 +02002281 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002282 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002283 if ffonly:
2284 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002285 if GitCommand(self, cmd).Wait() != 0:
2286 raise GitError('%s merge %s ' % (self.name, head))
2287
Kevin Degiabaa7f32014-11-12 11:27:45 -07002288 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002289 init_git_dir = not os.path.exists(self.gitdir)
2290 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002291 try:
2292 # Initialize the bare repository, which contains all of the objects.
2293 if init_obj_dir:
2294 os.makedirs(self.objdir)
2295 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002296
Kevin Degib1a07b82015-07-27 13:33:43 -06002297 # If we have a separate directory to hold refs, initialize it as well.
2298 if self.objdir != self.gitdir:
2299 if init_git_dir:
2300 os.makedirs(self.gitdir)
2301
2302 if init_obj_dir or init_git_dir:
2303 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2304 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002305 try:
2306 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2307 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002308 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002309 print("Retrying clone after deleting %s" %
2310 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002311 try:
2312 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002313 if self.worktree and os.path.exists(os.path.realpath
2314 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002315 shutil.rmtree(os.path.realpath(self.worktree))
2316 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2317 except:
2318 raise e
2319 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002320
Kevin Degi384b3c52014-10-16 16:02:58 -06002321 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002322 mp = self.manifest.manifestProject
2323 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002324
Kevin Degib1a07b82015-07-27 13:33:43 -06002325 if ref_dir or mirror_git:
2326 if not mirror_git:
2327 mirror_git = os.path.join(ref_dir, self.name + '.git')
2328 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2329 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002330
Kevin Degib1a07b82015-07-27 13:33:43 -06002331 if os.path.exists(mirror_git):
2332 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002333
Kevin Degib1a07b82015-07-27 13:33:43 -06002334 elif os.path.exists(repo_git):
2335 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002336
Kevin Degib1a07b82015-07-27 13:33:43 -06002337 else:
2338 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002339
Kevin Degib1a07b82015-07-27 13:33:43 -06002340 if ref_dir:
2341 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2342 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002343
Kevin Degib1a07b82015-07-27 13:33:43 -06002344 self._UpdateHooks()
2345
2346 m = self.manifest.manifestProject.config
2347 for key in ['user.name', 'user.email']:
2348 if m.Has(key, include_defaults=False):
2349 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002350 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002351 if self.manifest.IsMirror:
2352 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002353 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002354 self.config.SetString('core.bare', None)
2355 except Exception:
2356 if init_obj_dir and os.path.exists(self.objdir):
2357 shutil.rmtree(self.objdir)
2358 if init_git_dir and os.path.exists(self.gitdir):
2359 shutil.rmtree(self.gitdir)
2360 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361
Jimmie Westera0444582012-10-24 13:44:42 +02002362 def _UpdateHooks(self):
2363 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002364 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002365
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002366 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002367 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002368 if not os.path.exists(hooks):
2369 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002370 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002371 name = os.path.basename(stock_hook)
2372
Victor Boivie65e0f352011-04-18 11:23:29 +02002373 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002374 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002375 # Don't install a Gerrit Code Review hook if this
2376 # project does not appear to use it for reviews.
2377 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002378 # Since the manifest project is one of those, but also
2379 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002380 continue
2381
2382 dst = os.path.join(hooks, name)
2383 if os.path.islink(dst):
2384 continue
2385 if os.path.exists(dst):
2386 if filecmp.cmp(stock_hook, dst, shallow=False):
2387 os.remove(dst)
2388 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002389 _warn("%s: Not replacing locally modified %s hook",
2390 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002391 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002392 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002393 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002394 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002395 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002396 raise GitError('filesystem must support symlinks')
2397 else:
2398 raise
2399
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002401 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002402 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002403 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002404 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002405 remote.review = self.remote.review
2406 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002408 if self.worktree:
2409 remote.ResetFetch(mirror=False)
2410 else:
2411 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412 remote.Save()
2413
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002414 def _InitMRef(self):
2415 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002416 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002417
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002418 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002419 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002420
2421 def _InitAnyMRef(self, ref):
2422 cur = self.bare_ref.symref(ref)
2423
2424 if self.revisionId:
2425 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2426 msg = 'manifest set to %s' % self.revisionId
2427 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002428 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002429 else:
2430 remote = self.GetRemote(self.remote.name)
2431 dst = remote.ToLocal(self.revisionExpr)
2432 if cur != dst:
2433 msg = 'manifest set to %s' % self.revisionExpr
2434 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002435
Kevin Degi384b3c52014-10-16 16:02:58 -06002436 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002437 symlink_files = self.shareable_files[:]
2438 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002439 if share_refs:
2440 symlink_files += self.working_tree_files
2441 symlink_dirs += self.working_tree_dirs
2442 to_symlink = symlink_files + symlink_dirs
2443 for name in set(to_symlink):
2444 dst = os.path.realpath(os.path.join(destdir, name))
2445 if os.path.lexists(dst):
2446 src = os.path.realpath(os.path.join(srcdir, name))
2447 # Fail if the links are pointing to the wrong place
2448 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002449 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002450 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002451 'work tree. If you\'re comfortable with the '
2452 'possibility of losing the work tree\'s git metadata,'
2453 ' use `repo sync --force-sync {0}` to '
2454 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002455
David James8d201162013-10-11 17:03:19 -07002456 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2457 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2458
2459 Args:
2460 gitdir: The bare git repository. Must already be initialized.
2461 dotgit: The repository you would like to initialize.
2462 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2463 Only one work tree can store refs under a given |gitdir|.
2464 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2465 This saves you the effort of initializing |dotgit| yourself.
2466 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002467 symlink_files = self.shareable_files[:]
2468 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002469 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002470 symlink_files += self.working_tree_files
2471 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002472 to_symlink = symlink_files + symlink_dirs
2473
2474 to_copy = []
2475 if copy_all:
2476 to_copy = os.listdir(gitdir)
2477
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002478 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002479 for name in set(to_copy).union(to_symlink):
2480 try:
2481 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002482 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002483
Kevin Degi384b3c52014-10-16 16:02:58 -06002484 if os.path.lexists(dst):
2485 continue
David James8d201162013-10-11 17:03:19 -07002486
2487 # If the source dir doesn't exist, create an empty dir.
2488 if name in symlink_dirs and not os.path.lexists(src):
2489 os.makedirs(src)
2490
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002491 if name in to_symlink:
2492 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2493 elif copy_all and not os.path.islink(dst):
2494 if os.path.isdir(src):
2495 shutil.copytree(src, dst)
2496 elif os.path.isfile(src):
2497 shutil.copy(src, dst)
2498
Conley Owens80b87fe2014-05-09 17:13:44 -07002499 # If the source file doesn't exist, ensure the destination
2500 # file doesn't either.
2501 if name in symlink_files and not os.path.lexists(src):
2502 try:
2503 os.remove(dst)
2504 except OSError:
2505 pass
2506
David James8d201162013-10-11 17:03:19 -07002507 except OSError as e:
2508 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002509 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002510 else:
2511 raise
2512
Martin Kellye4e94d22017-03-21 16:05:12 -07002513 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002514 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002515 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002516 try:
2517 if init_dotgit:
2518 os.makedirs(dotgit)
2519 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2520 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002521
Kevin Degiabaa7f32014-11-12 11:27:45 -07002522 try:
2523 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2524 except GitError as e:
2525 if force_sync:
2526 try:
2527 shutil.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002528 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002529 except:
2530 raise e
2531 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002532
Kevin Degib1a07b82015-07-27 13:33:43 -06002533 if init_dotgit:
2534 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535
Kevin Degib1a07b82015-07-27 13:33:43 -06002536 cmd = ['read-tree', '--reset', '-u']
2537 cmd.append('-v')
2538 cmd.append(HEAD)
2539 if GitCommand(self, cmd).Wait() != 0:
2540 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002541
Martin Kellye4e94d22017-03-21 16:05:12 -07002542 if submodules:
2543 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002544 self._CopyAndLinkFiles()
2545 except Exception:
2546 if init_dotgit:
2547 shutil.rmtree(dotgit)
2548 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549
2550 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002551 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002553 def _revlist(self, *args, **kw):
2554 a = []
2555 a.extend(args)
2556 a.append('--')
2557 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002558
2559 @property
2560 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002561 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002562
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002563 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002564 """Get logs between two revisions of this project."""
2565 comp = '..'
2566 if rev1:
2567 revs = [rev1]
2568 if rev2:
2569 revs.extend([comp, rev2])
2570 cmd = ['log', ''.join(revs)]
2571 out = DiffColoring(self.config)
2572 if out.is_on and color:
2573 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002574 if pretty_format is not None:
2575 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002576 if oneline:
2577 cmd.append('--oneline')
2578
2579 try:
2580 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2581 if log.Wait() == 0:
2582 return log.stdout
2583 except GitError:
2584 # worktree may not exist if groups changed for example. In that case,
2585 # try in gitdir instead.
2586 if not os.path.exists(self.worktree):
2587 return self.bare_git.log(*cmd[1:])
2588 else:
2589 raise
2590 return None
2591
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002592 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2593 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002594 """Get the list of logs from this revision to given revisionId"""
2595 logs = {}
2596 selfId = self.GetRevisionId(self._allrefs)
2597 toId = toProject.GetRevisionId(toProject._allrefs)
2598
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002599 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2600 pretty_format=pretty_format)
2601 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2602 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002603 return logs
2604
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002606
David James8d201162013-10-11 17:03:19 -07002607 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 self._project = project
2609 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002610 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 def LsOthers(self):
2613 p = GitCommand(self._project,
2614 ['ls-files',
2615 '-z',
2616 '--others',
2617 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002618 bare=False,
David James8d201162013-10-11 17:03:19 -07002619 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002620 capture_stdout=True,
2621 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622 if p.Wait() == 0:
2623 out = p.stdout
2624 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002625 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002626 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002627 return []
2628
2629 def DiffZ(self, name, *args):
2630 cmd = [name]
2631 cmd.append('-z')
2632 cmd.extend(args)
2633 p = GitCommand(self._project,
2634 cmd,
David James8d201162013-10-11 17:03:19 -07002635 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002636 bare=False,
2637 capture_stdout=True,
2638 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002639 try:
2640 out = p.process.stdout.read()
2641 r = {}
2642 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002643 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002644 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002645 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002646 info = next(out)
2647 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002648 except StopIteration:
2649 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650
2651 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002652
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002653 def __init__(self, path, omode, nmode, oid, nid, state):
2654 self.path = path
2655 self.src_path = None
2656 self.old_mode = omode
2657 self.new_mode = nmode
2658 self.old_id = oid
2659 self.new_id = nid
2660
2661 if len(state) == 1:
2662 self.status = state
2663 self.level = None
2664 else:
2665 self.status = state[:1]
2666 self.level = state[1:]
2667 while self.level.startswith('0'):
2668 self.level = self.level[1:]
2669
2670 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002671 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002672 if info.status in ('R', 'C'):
2673 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002674 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675 r[info.path] = info
2676 return r
2677 finally:
2678 p.Wait()
2679
2680 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002681 if self._bare:
2682 path = os.path.join(self._project.gitdir, HEAD)
2683 else:
2684 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002685 try:
2686 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002687 except IOError as e:
2688 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002689 try:
2690 line = fd.read()
2691 finally:
2692 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302693 try:
2694 line = line.decode()
2695 except AttributeError:
2696 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002697 if line.startswith('ref: '):
2698 return line[5:-1]
2699 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700
2701 def SetHead(self, ref, message=None):
2702 cmdv = []
2703 if message is not None:
2704 cmdv.extend(['-m', message])
2705 cmdv.append(HEAD)
2706 cmdv.append(ref)
2707 self.symbolic_ref(*cmdv)
2708
2709 def DetachHead(self, new, message=None):
2710 cmdv = ['--no-deref']
2711 if message is not None:
2712 cmdv.extend(['-m', message])
2713 cmdv.append(HEAD)
2714 cmdv.append(new)
2715 self.update_ref(*cmdv)
2716
2717 def UpdateRef(self, name, new, old=None,
2718 message=None,
2719 detach=False):
2720 cmdv = []
2721 if message is not None:
2722 cmdv.extend(['-m', message])
2723 if detach:
2724 cmdv.append('--no-deref')
2725 cmdv.append(name)
2726 cmdv.append(new)
2727 if old is not None:
2728 cmdv.append(old)
2729 self.update_ref(*cmdv)
2730
2731 def DeleteRef(self, name, old=None):
2732 if not old:
2733 old = self.rev_parse(name)
2734 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002735 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002736
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002737 def rev_list(self, *args, **kw):
2738 if 'format' in kw:
2739 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2740 else:
2741 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002742 cmdv.extend(args)
2743 p = GitCommand(self._project,
2744 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002745 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002746 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002747 capture_stdout=True,
2748 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002749 r = []
2750 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002751 if line[-1] == '\n':
2752 line = line[:-1]
2753 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002754 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002755 raise GitError('%s rev-list %s: %s' %
2756 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002757 return r
2758
2759 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002760 """Allow arbitrary git commands using pythonic syntax.
2761
2762 This allows you to do things like:
2763 git_obj.rev_parse('HEAD')
2764
2765 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2766 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002767 Any other positional arguments will be passed to the git command, and the
2768 following keyword arguments are supported:
2769 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002770
2771 Args:
2772 name: The name of the git command to call. Any '_' characters will
2773 be replaced with '-'.
2774
2775 Returns:
2776 A callable object that will try to call git with the named command.
2777 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002778 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002779
Dave Borowitz091f8932012-10-23 17:01:04 -07002780 def runner(*args, **kwargs):
2781 cmdv = []
2782 config = kwargs.pop('config', None)
2783 for k in kwargs:
2784 raise TypeError('%s() got an unexpected keyword argument %r'
2785 % (name, k))
2786 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002787 if not git_require((1, 7, 2)):
2788 raise ValueError('cannot set config on command line for %s()'
2789 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302790 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002791 cmdv.append('-c')
2792 cmdv.append('%s=%s' % (k, v))
2793 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002794 cmdv.extend(args)
2795 p = GitCommand(self._project,
2796 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002797 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002798 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002799 capture_stdout=True,
2800 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002801 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002802 raise GitError('%s %s: %s' %
2803 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002804 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302805 try:
Conley Owensedd01512013-09-26 12:59:58 -07002806 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302807 except AttributeError:
2808 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2810 return r[:-1]
2811 return r
2812 return runner
2813
2814
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002815class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002816
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002817 def __str__(self):
2818 return 'prior sync failed; rebase still in progress'
2819
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002820
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002821class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002822
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002823 def __str__(self):
2824 return 'contains uncommitted changes'
2825
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002826
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002827class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002828
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002829 def __init__(self, project, text):
2830 self.project = project
2831 self.text = text
2832
2833 def Print(self, syncbuf):
2834 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2835 syncbuf.out.nl()
2836
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002837
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002838class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002839
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002840 def __init__(self, project, why):
2841 self.project = project
2842 self.why = why
2843
2844 def Print(self, syncbuf):
2845 syncbuf.out.fail('error: %s/: %s',
2846 self.project.relpath,
2847 str(self.why))
2848 syncbuf.out.nl()
2849
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002850
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002851class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002852
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002853 def __init__(self, project, action):
2854 self.project = project
2855 self.action = action
2856
2857 def Run(self, syncbuf):
2858 out = syncbuf.out
2859 out.project('project %s/', self.project.relpath)
2860 out.nl()
2861 try:
2862 self.action()
2863 out.nl()
2864 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002865 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002866 out.nl()
2867 return False
2868
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002869
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002870class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002871
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002872 def __init__(self, config):
2873 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002874 self.project = self.printer('header', attr='bold')
2875 self.info = self.printer('info')
2876 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002877
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002878
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002879class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002880
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002881 def __init__(self, config, detach_head=False):
2882 self._messages = []
2883 self._failures = []
2884 self._later_queue1 = []
2885 self._later_queue2 = []
2886
2887 self.out = _SyncColoring(config)
2888 self.out.redirect(sys.stderr)
2889
2890 self.detach_head = detach_head
2891 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002892 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002893
2894 def info(self, project, fmt, *args):
2895 self._messages.append(_InfoMessage(project, fmt % args))
2896
2897 def fail(self, project, err=None):
2898 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002899 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002900
2901 def later1(self, project, what):
2902 self._later_queue1.append(_Later(project, what))
2903
2904 def later2(self, project, what):
2905 self._later_queue2.append(_Later(project, what))
2906
2907 def Finish(self):
2908 self._PrintMessages()
2909 self._RunLater()
2910 self._PrintMessages()
2911 return self.clean
2912
David Rileye0684ad2017-04-05 00:02:59 -07002913 def Recently(self):
2914 recent_clean = self.recent_clean
2915 self.recent_clean = True
2916 return recent_clean
2917
2918 def _MarkUnclean(self):
2919 self.clean = False
2920 self.recent_clean = False
2921
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002922 def _RunLater(self):
2923 for q in ['_later_queue1', '_later_queue2']:
2924 if not self._RunQueue(q):
2925 return
2926
2927 def _RunQueue(self, queue):
2928 for m in getattr(self, queue):
2929 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07002930 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002931 return False
2932 setattr(self, queue, [])
2933 return True
2934
2935 def _PrintMessages(self):
2936 for m in self._messages:
2937 m.Print(self)
2938 for m in self._failures:
2939 m.Print(self)
2940
2941 self._messages = []
2942 self._failures = []
2943
2944
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002945class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002946
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002947 """A special project housed under .repo.
2948 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002949
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002950 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002951 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002952 manifest=manifest,
2953 name=name,
2954 gitdir=gitdir,
2955 objdir=gitdir,
2956 worktree=worktree,
2957 remote=RemoteSpec('origin'),
2958 relpath='.repo/%s' % name,
2959 revisionExpr='refs/heads/master',
2960 revisionId=None,
2961 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962
2963 def PreSync(self):
2964 if self.Exists:
2965 cb = self.CurrentBranch
2966 if cb:
2967 base = self.GetBranch(cb).merge
2968 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002969 self.revisionExpr = base
2970 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971
Martin Kelly224a31a2017-07-10 14:46:25 -07002972 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02002973 """ Prepare MetaProject for manifest branch switch
2974 """
2975
2976 # detach and delete manifest branch, allowing a new
2977 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002978 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07002979 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02002980 syncbuf.Finish()
2981
2982 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002983 ['update-ref', '-d', 'refs/heads/default'],
2984 capture_stdout=True,
2985 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002986
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002987 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002988 def LastFetch(self):
2989 try:
2990 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2991 return os.path.getmtime(fh)
2992 except OSError:
2993 return 0
2994
2995 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002996 def HasChanges(self):
2997 """Has the remote received new commits not yet checked out?
2998 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002999 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003000 return False
3001
David Pursehouse8a68ff92012-09-24 12:15:13 +09003002 all_refs = self.bare_ref.all
3003 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003004 head = self.work_git.GetHead()
3005 if head.startswith(R_HEADS):
3006 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003007 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003008 except KeyError:
3009 head = None
3010
3011 if revid == head:
3012 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003013 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003014 return True
3015 return False