blob: c2cccb4f4129f6f1fd032d2df35ffd14bb98b2e6 [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,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200181 dest_branch=None,
182 validate_certs=True):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800183 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700184 people,
Brian Harring435370c2012-07-28 15:37:04 -0700185 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000186 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200187 private=private,
188 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200189 dest_branch=dest_branch,
190 validate_certs=validate_certs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700192 def GetPublishedRefs(self):
193 refs = {}
194 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700195 self.branch.remote.SshReviewUrl(self.project.UserEmail),
196 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700197 for line in output.split('\n'):
198 try:
199 (sha, ref) = line.split()
200 refs[sha] = ref
201 except ValueError:
202 pass
203
204 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700206
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700208
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209 def __init__(self, config):
210 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100211 self.project = self.printer('header', attr='bold')
212 self.branch = self.printer('header', attr='bold')
213 self.nobranch = self.printer('nobranch', fg='red')
214 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700215
Anthony King7bdac712014-07-16 12:56:40 +0100216 self.added = self.printer('added', fg='green')
217 self.changed = self.printer('changed', fg='red')
218 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219
220
221class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223 def __init__(self, config):
224 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100225 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227
Anthony King7bdac712014-07-16 12:56:40 +0100228class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229
James W. Mills24c13082012-04-12 15:04:13 -0500230 def __init__(self, name, value, keep):
231 self.name = name
232 self.value = value
233 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
Anthony King7bdac712014-07-16 12:56:40 +0100236class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700237
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800238 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239 self.src = src
240 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800241 self.abs_src = abssrc
242 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243
244 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800245 src = self.abs_src
246 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 # copy file if it does not exist or is out of date
248 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
249 try:
250 # remove existing file first, since it might be read-only
251 if os.path.exists(dest):
252 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400253 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200254 dest_dir = os.path.dirname(dest)
255 if not os.path.isdir(dest_dir):
256 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 shutil.copy(src, dest)
258 # make the file read-only
259 mode = os.stat(dest)[stat.ST_MODE]
260 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
261 os.chmod(dest, mode)
262 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700263 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700265
Anthony King7bdac712014-07-16 12:56:40 +0100266class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700267
Wink Saville4c426ef2015-06-03 08:05:17 -0700268 def __init__(self, git_worktree, src, dest, relsrc, absdest):
269 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500270 self.src = src
271 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700272 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500273 self.abs_dest = absdest
274
Wink Saville4c426ef2015-06-03 08:05:17 -0700275 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700277 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500278 try:
279 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800280 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700281 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500282 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700283 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500284 if not os.path.isdir(dest_dir):
285 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700286 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500287 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700288 _error('Cannot link file %s to %s', relSrc, absDest)
289
290 def _Link(self):
291 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
292 on the src linking all of the files in the source in to the destination
293 directory.
294 """
295 # We use the absSrc to handle the situation where the current directory
296 # is not the root of the repo
297 absSrc = os.path.join(self.git_worktree, self.src)
298 if os.path.exists(absSrc):
299 # Entity exists so just a simple one to one link operation
300 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
301 else:
302 # Entity doesn't exist assume there is a wild card
303 absDestDir = self.abs_dest
304 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
305 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700306 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700307 else:
308 absSrcFiles = glob.glob(absSrc)
309 for absSrcFile in absSrcFiles:
310 # Create a releative path from source dir to destination dir
311 absSrcDir = os.path.dirname(absSrcFile)
312 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
313
314 # Get the source file name
315 srcFile = os.path.basename(absSrcFile)
316
317 # Now form the final full paths to srcFile. They will be
318 # absolute for the desintaiton and relative for the srouce.
319 absDest = os.path.join(absDestDir, srcFile)
320 relSrc = os.path.join(relSrcDir, srcFile)
321 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500322
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700323
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700324class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700325
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700326 def __init__(self,
327 name,
Anthony King7bdac712014-07-16 12:56:40 +0100328 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700329 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100330 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700331 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700332 orig_name=None,
333 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700334 self.name = name
335 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700336 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700337 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100338 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700339 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700340 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700342
Doug Anderson37282b42011-03-04 11:54:18 -0800343class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700344
Doug Anderson37282b42011-03-04 11:54:18 -0800345 """A RepoHook contains information about a script to run as a hook.
346
347 Hooks are used to run a python script before running an upload (for instance,
348 to run presubmit checks). Eventually, we may have hooks for other actions.
349
350 This shouldn't be confused with files in the 'repo/hooks' directory. Those
351 files are copied into each '.git/hooks' folder for each project. Repo-level
352 hooks are associated instead with repo actions.
353
354 Hooks are always python. When a hook is run, we will load the hook into the
355 interpreter and execute its main() function.
356 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700357
Doug Anderson37282b42011-03-04 11:54:18 -0800358 def __init__(self,
359 hook_type,
360 hooks_project,
361 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400362 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800363 abort_if_user_denies=False):
364 """RepoHook constructor.
365
366 Params:
367 hook_type: A string representing the type of hook. This is also used
368 to figure out the name of the file containing the hook. For
369 example: 'pre-upload'.
370 hooks_project: The project containing the repo hooks. If you have a
371 manifest, this is manifest.repo_hooks_project. OK if this is None,
372 which will make the hook a no-op.
373 topdir: Repo's top directory (the one containing the .repo directory).
374 Scripts will run with CWD as this directory. If you have a manifest,
375 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400376 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800377 abort_if_user_denies: If True, we'll throw a HookError() if the user
378 doesn't allow us to run the hook.
379 """
380 self._hook_type = hook_type
381 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400382 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800383 self._topdir = topdir
384 self._abort_if_user_denies = abort_if_user_denies
385
386 # Store the full path to the script for convenience.
387 if self._hooks_project:
388 self._script_fullpath = os.path.join(self._hooks_project.worktree,
389 self._hook_type + '.py')
390 else:
391 self._script_fullpath = None
392
393 def _GetHash(self):
394 """Return a hash of the contents of the hooks directory.
395
396 We'll just use git to do this. This hash has the property that if anything
397 changes in the directory we will return a different has.
398
399 SECURITY CONSIDERATION:
400 This hash only represents the contents of files in the hook directory, not
401 any other files imported or called by hooks. Changes to imported files
402 can change the script behavior without affecting the hash.
403
404 Returns:
405 A string representing the hash. This will always be ASCII so that it can
406 be printed to the user easily.
407 """
408 assert self._hooks_project, "Must have hooks to calculate their hash."
409
410 # We will use the work_git object rather than just calling GetRevisionId().
411 # That gives us a hash of the latest checked in version of the files that
412 # the user will actually be executing. Specifically, GetRevisionId()
413 # doesn't appear to change even if a user checks out a different version
414 # of the hooks repo (via git checkout) nor if a user commits their own revs.
415 #
416 # NOTE: Local (non-committed) changes will not be factored into this hash.
417 # I think this is OK, since we're really only worried about warning the user
418 # about upstream changes.
419 return self._hooks_project.work_git.rev_parse('HEAD')
420
421 def _GetMustVerb(self):
422 """Return 'must' if the hook is required; 'should' if not."""
423 if self._abort_if_user_denies:
424 return 'must'
425 else:
426 return 'should'
427
428 def _CheckForHookApproval(self):
429 """Check to see whether this hook has been approved.
430
Mike Frysinger40252c22016-08-15 21:23:44 -0400431 We'll accept approval of manifest URLs if they're using secure transports.
432 This way the user can say they trust the manifest hoster. For insecure
433 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800434
435 Note that we ask permission for each individual hook even though we use
436 the hash of all hooks when detecting changes. We'd like the user to be
437 able to approve / deny each hook individually. We only use the hash of all
438 hooks because there is no other easy way to detect changes to local imports.
439
440 Returns:
441 True if this hook is approved to run; False otherwise.
442
443 Raises:
444 HookError: Raised if the user doesn't approve and abort_if_user_denies
445 was passed to the consturctor.
446 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400447 if self._ManifestUrlHasSecureScheme():
448 return self._CheckForHookApprovalManifest()
449 else:
450 return self._CheckForHookApprovalHash()
451
452 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
453 changed_prompt):
454 """Check for approval for a particular attribute and hook.
455
456 Args:
457 subkey: The git config key under [repo.hooks.<hook_type>] to store the
458 last approved string.
459 new_val: The new value to compare against the last approved one.
460 main_prompt: Message to display to the user to ask for approval.
461 changed_prompt: Message explaining why we're re-asking for approval.
462
463 Returns:
464 True if this hook is approved to run; False otherwise.
465
466 Raises:
467 HookError: Raised if the user doesn't approve and abort_if_user_denies
468 was passed to the consturctor.
469 """
Doug Anderson37282b42011-03-04 11:54:18 -0800470 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400471 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800472
Mike Frysinger40252c22016-08-15 21:23:44 -0400473 # Get the last value that the user approved for this hook; may be None.
474 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800475
Mike Frysinger40252c22016-08-15 21:23:44 -0400476 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800477 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400478 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800479 # Approval matched. We're done.
480 return True
481 else:
482 # Give the user a reason why we're prompting, since they last told
483 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400484 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800485 else:
486 prompt = ''
487
488 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
489 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400490 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530491 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900492 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800493
494 # User is doing a one-time approval.
495 if response in ('y', 'yes'):
496 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400497 elif response == 'always':
498 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800499 return True
500
501 # For anything else, we'll assume no approval.
502 if self._abort_if_user_denies:
503 raise HookError('You must allow the %s hook or use --no-verify.' %
504 self._hook_type)
505
506 return False
507
Mike Frysinger40252c22016-08-15 21:23:44 -0400508 def _ManifestUrlHasSecureScheme(self):
509 """Check if the URI for the manifest is a secure transport."""
510 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
511 parse_results = urllib.parse.urlparse(self._manifest_url)
512 return parse_results.scheme in secure_schemes
513
514 def _CheckForHookApprovalManifest(self):
515 """Check whether the user has approved this manifest host.
516
517 Returns:
518 True if this hook is approved to run; False otherwise.
519 """
520 return self._CheckForHookApprovalHelper(
521 'approvedmanifest',
522 self._manifest_url,
523 'Run hook scripts from %s' % (self._manifest_url,),
524 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
525
526 def _CheckForHookApprovalHash(self):
527 """Check whether the user has approved the hooks repo.
528
529 Returns:
530 True if this hook is approved to run; False otherwise.
531 """
532 prompt = ('Repo %s run the script:\n'
533 ' %s\n'
534 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700535 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400536 return self._CheckForHookApprovalHelper(
537 'approvedhash',
538 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700539 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400540 'Scripts have changed since %s was allowed.' % (self._hook_type,))
541
Doug Anderson37282b42011-03-04 11:54:18 -0800542 def _ExecuteHook(self, **kwargs):
543 """Actually execute the given hook.
544
545 This will run the hook's 'main' function in our python interpreter.
546
547 Args:
548 kwargs: Keyword arguments to pass to the hook. These are often specific
549 to the hook type. For instance, pre-upload hooks will contain
550 a project_list.
551 """
552 # Keep sys.path and CWD stashed away so that we can always restore them
553 # upon function exit.
554 orig_path = os.getcwd()
555 orig_syspath = sys.path
556
557 try:
558 # Always run hooks with CWD as topdir.
559 os.chdir(self._topdir)
560
561 # Put the hook dir as the first item of sys.path so hooks can do
562 # relative imports. We want to replace the repo dir as [0] so
563 # hooks can't import repo files.
564 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
565
566 # Exec, storing global context in the context dict. We catch exceptions
567 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500568 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800569 try:
Anthony King70f68902014-05-05 21:15:34 +0100570 exec(compile(open(self._script_fullpath).read(),
571 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800572 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700573 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
574 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800575
576 # Running the script should have defined a main() function.
577 if 'main' not in context:
578 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
579
Doug Anderson37282b42011-03-04 11:54:18 -0800580 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
581 # We don't actually want hooks to define their main with this argument--
582 # it's there to remind them that their hook should always take **kwargs.
583 # For instance, a pre-upload hook should be defined like:
584 # def main(project_list, **kwargs):
585 #
586 # This allows us to later expand the API without breaking old hooks.
587 kwargs = kwargs.copy()
588 kwargs['hook_should_take_kwargs'] = True
589
590 # Call the main function in the hook. If the hook should cause the
591 # build to fail, it will raise an Exception. We'll catch that convert
592 # to a HookError w/ just the failing traceback.
593 try:
594 context['main'](**kwargs)
595 except Exception:
596 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700597 'above.' % (traceback.format_exc(),
598 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800599 finally:
600 # Restore sys.path and CWD.
601 sys.path = orig_syspath
602 os.chdir(orig_path)
603
604 def Run(self, user_allows_all_hooks, **kwargs):
605 """Run the hook.
606
607 If the hook doesn't exist (because there is no hooks project or because
608 this particular hook is not enabled), this is a no-op.
609
610 Args:
611 user_allows_all_hooks: If True, we will never prompt about running the
612 hook--we'll just assume it's OK to run it.
613 kwargs: Keyword arguments to pass to the hook. These are often specific
614 to the hook type. For instance, pre-upload hooks will contain
615 a project_list.
616
617 Raises:
618 HookError: If there was a problem finding the hook or the user declined
619 to run a required hook (from _CheckForHookApproval).
620 """
621 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700622 if ((not self._hooks_project) or (self._hook_type not in
623 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800624 return
625
626 # Bail with a nice error if we can't find the hook.
627 if not os.path.isfile(self._script_fullpath):
628 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
629
630 # Make sure the user is OK with running the hook.
631 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
632 return
633
634 # Run the hook with the same version of python we're using.
635 self._ExecuteHook(**kwargs)
636
637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600639 # These objects can be shared between several working trees.
640 shareable_files = ['description', 'info']
641 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
642 # These objects can only be used by a single working tree.
643 working_tree_files = ['config', 'packed-refs', 'shallow']
644 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700645
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646 def __init__(self,
647 manifest,
648 name,
649 remote,
650 gitdir,
David James8d201162013-10-11 17:03:19 -0700651 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700652 worktree,
653 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700654 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800655 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100656 rebase=True,
657 groups=None,
658 sync_c=False,
659 sync_s=False,
660 clone_depth=None,
661 upstream=None,
662 parent=None,
663 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900664 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700665 optimized_fetch=False,
666 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800667 """Init a Project object.
668
669 Args:
670 manifest: The XmlManifest object.
671 name: The `name` attribute of manifest.xml's project element.
672 remote: RemoteSpec object specifying its remote's properties.
673 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700674 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800675 worktree: Absolute path of git working tree.
676 relpath: Relative path of git working tree to repo's top directory.
677 revisionExpr: The `revision` attribute of manifest.xml's project element.
678 revisionId: git commit id for checking out.
679 rebase: The `rebase` attribute of manifest.xml's project element.
680 groups: The `groups` attribute of manifest.xml's project element.
681 sync_c: The `sync-c` attribute of manifest.xml's project element.
682 sync_s: The `sync-s` attribute of manifest.xml's project element.
683 upstream: The `upstream` attribute of manifest.xml's project element.
684 parent: The parent Project object.
685 is_derived: False if the project was explicitly defined in the manifest;
686 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400687 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900688 optimized_fetch: If True, when a project is set to a sha1 revision, only
689 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700690 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800691 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692 self.manifest = manifest
693 self.name = name
694 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800695 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700696 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800697 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700698 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800699 else:
700 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700702 self.revisionExpr = revisionExpr
703
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700704 if revisionId is None \
705 and revisionExpr \
706 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700707 self.revisionId = revisionExpr
708 else:
709 self.revisionId = revisionId
710
Mike Pontillod3153822012-02-28 11:53:24 -0800711 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700712 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700713 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800714 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900715 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700716 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800717 self.parent = parent
718 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900719 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800720 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800721
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500724 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500725 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700726 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
727 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800729 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700730 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800731 else:
732 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700733 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700734 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700735 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400736 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700737 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738
Doug Anderson37282b42011-03-04 11:54:18 -0800739 # This will be filled in if a project is later identified to be the
740 # project containing repo hooks.
741 self.enabled_repo_hooks = []
742
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800744 def Derived(self):
745 return self.is_derived
746
747 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600749 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750
751 @property
752 def CurrentBranch(self):
753 """Obtain the name of the currently checked out branch.
754 The branch name omits the 'refs/heads/' prefix.
755 None is returned if the project is on a detached HEAD.
756 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700757 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 if b.startswith(R_HEADS):
759 return b[len(R_HEADS):]
760 return None
761
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700762 def IsRebaseInProgress(self):
763 w = self.worktree
764 g = os.path.join(w, '.git')
765 return os.path.exists(os.path.join(g, 'rebase-apply')) \
766 or os.path.exists(os.path.join(g, 'rebase-merge')) \
767 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 def IsDirty(self, consider_untracked=True):
770 """Is the working directory modified in some way?
771 """
772 self.work_git.update_index('-q',
773 '--unmerged',
774 '--ignore-missing',
775 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900776 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 return True
778 if self.work_git.DiffZ('diff-files'):
779 return True
780 if consider_untracked and self.work_git.LsOthers():
781 return True
782 return False
783
784 _userident_name = None
785 _userident_email = None
786
787 @property
788 def UserName(self):
789 """Obtain the user's personal name.
790 """
791 if self._userident_name is None:
792 self._LoadUserIdentity()
793 return self._userident_name
794
795 @property
796 def UserEmail(self):
797 """Obtain the user's email address. This is very likely
798 to be their Gerrit login.
799 """
800 if self._userident_email is None:
801 self._LoadUserIdentity()
802 return self._userident_email
803
804 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900805 u = self.bare_git.var('GIT_COMMITTER_IDENT')
806 m = re.compile("^(.*) <([^>]*)> ").match(u)
807 if m:
808 self._userident_name = m.group(1)
809 self._userident_email = m.group(2)
810 else:
811 self._userident_name = ''
812 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813
814 def GetRemote(self, name):
815 """Get the configuration for a single remote.
816 """
817 return self.config.GetRemote(name)
818
819 def GetBranch(self, name):
820 """Get the configuration for a single branch.
821 """
822 return self.config.GetBranch(name)
823
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700824 def GetBranches(self):
825 """Get all existing local branches.
826 """
827 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900828 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700829 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700830
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530831 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700832 if name.startswith(R_HEADS):
833 name = name[len(R_HEADS):]
834 b = self.GetBranch(name)
835 b.current = name == current
836 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900837 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700838 heads[name] = b
839
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530840 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700841 if name.startswith(R_PUB):
842 name = name[len(R_PUB):]
843 b = heads.get(name)
844 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900845 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700846
847 return heads
848
Colin Cross5acde752012-03-28 20:15:45 -0700849 def MatchesGroups(self, manifest_groups):
850 """Returns true if the manifest groups specified at init should cause
851 this project to be synced.
852 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700853 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700854
855 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700856 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700857 manifest_groups: "-group1,group2"
858 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500859
860 The special manifest group "default" will match any project that
861 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700862 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500863 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700864 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700865 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500866 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700867
Conley Owens971de8e2012-04-16 10:36:08 -0700868 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700869 for group in expanded_manifest_groups:
870 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700871 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700872 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700873 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700874
Conley Owens971de8e2012-04-16 10:36:08 -0700875 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700877# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700878 def UncommitedFiles(self, get_all=True):
879 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700881 Args:
882 get_all: a boolean, if True - get information about all different
883 uncommitted files. If False - return as soon as any kind of
884 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500885 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700886 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500887 self.work_git.update_index('-q',
888 '--unmerged',
889 '--ignore-missing',
890 '--refresh')
891 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700892 details.append("rebase in progress")
893 if not get_all:
894 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500895
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700896 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
897 if changes:
898 details.extend(changes)
899 if not get_all:
900 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500901
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700902 changes = self.work_git.DiffZ('diff-files').keys()
903 if changes:
904 details.extend(changes)
905 if not get_all:
906 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500907
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700908 changes = self.work_git.LsOthers()
909 if changes:
910 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500911
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700912 return details
913
914 def HasChanges(self):
915 """Returns true if there are uncommitted changes.
916 """
917 if self.UncommitedFiles(get_all=False):
918 return True
919 else:
920 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500921
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600922 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200924
925 Args:
926 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600927 quiet: If True then only print the project name. Do not print
928 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 """
930 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700931 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200932 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700933 print(file=output_redir)
934 print('project %s/' % self.relpath, file=output_redir)
935 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 return
937
938 self.work_git.update_index('-q',
939 '--unmerged',
940 '--ignore-missing',
941 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700942 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
944 df = self.work_git.DiffZ('diff-files')
945 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100946 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700947 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
949 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700950 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200951 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700952 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600954 if quiet:
955 out.nl()
956 return 'DIRTY'
957
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 branch = self.CurrentBranch
959 if branch is None:
960 out.nobranch('(*** NO BRANCH ***)')
961 else:
962 out.branch('branch %s', branch)
963 out.nl()
964
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700965 if rb:
966 out.important('prior sync failed; rebase still in progress')
967 out.nl()
968
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 paths = list()
970 paths.extend(di.keys())
971 paths.extend(df.keys())
972 paths.extend(do)
973
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900975 try:
976 i = di[p]
977 except KeyError:
978 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900980 try:
981 f = df[p]
982 except KeyError:
983 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200984
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900985 if i:
986 i_status = i.status.upper()
987 else:
988 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900990 if f:
991 f_status = f.status.lower()
992 else:
993 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994
995 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800996 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700997 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 else:
999 line = ' %s%s\t%s' % (i_status, f_status, p)
1000
1001 if i and not f:
1002 out.added('%s', line)
1003 elif (i and f) or (not i and f):
1004 out.changed('%s', line)
1005 elif not i and not f:
1006 out.untracked('%s', line)
1007 else:
1008 out.write('%s', line)
1009 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001010
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001011 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012
pelyad67872d2012-03-28 14:49:58 +03001013 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014 """Prints the status of the repository to stdout.
1015 """
1016 out = DiffColoring(self.config)
1017 cmd = ['diff']
1018 if out.is_on:
1019 cmd.append('--color')
1020 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001021 if absolute_paths:
1022 cmd.append('--src-prefix=a/%s/' % self.relpath)
1023 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 cmd.append('--')
1025 p = GitCommand(self,
1026 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001027 capture_stdout=True,
1028 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 has_diff = False
1030 for line in p.process.stdout:
1031 if not has_diff:
1032 out.nl()
1033 out.project('project %s/' % self.relpath)
1034 out.nl()
1035 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001036 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 p.Wait()
1038
1039
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001040# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041
David Pursehouse8a68ff92012-09-24 12:15:13 +09001042 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043 """Was the branch published (uploaded) for code review?
1044 If so, returns the SHA-1 hash of the last published
1045 state for the branch.
1046 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001047 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001049 try:
1050 return self.bare_git.rev_parse(key)
1051 except GitError:
1052 return None
1053 else:
1054 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001055 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001056 except KeyError:
1057 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058
David Pursehouse8a68ff92012-09-24 12:15:13 +09001059 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 """Prunes any stale published refs.
1061 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001062 if all_refs is None:
1063 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 heads = set()
1065 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301066 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 if name.startswith(R_HEADS):
1068 heads.add(name)
1069 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001070 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301072 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 n = name[len(R_PUB):]
1074 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001075 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001077 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 """List any branches which can be uploaded for review.
1079 """
1080 heads = {}
1081 pubed = {}
1082
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301083 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001085 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001087 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088
1089 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301090 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001091 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001093 if selected_branch and branch != selected_branch:
1094 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001096 rb = self.GetUploadableBranch(branch)
1097 if rb:
1098 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 return ready
1100
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001101 def GetUploadableBranch(self, branch_name):
1102 """Get a single uploadable branch, or None.
1103 """
1104 branch = self.GetBranch(branch_name)
1105 base = branch.LocalMerge
1106 if branch.LocalMerge:
1107 rb = ReviewableBranch(self, branch, base)
1108 if rb.commits:
1109 return rb
1110 return None
1111
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001112 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001113 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001114 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001115 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001116 private=False,
1117 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001118 dest_branch=None,
1119 validate_certs=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120 """Uploads the named branch for code review.
1121 """
1122 if branch is None:
1123 branch = self.CurrentBranch
1124 if branch is None:
1125 raise GitError('not currently on a branch')
1126
1127 branch = self.GetBranch(branch)
1128 if not branch.LocalMerge:
1129 raise GitError('branch %s does not track a remote' % branch.name)
1130 if not branch.remote.review:
1131 raise GitError('remote %s has no review url' % branch.remote.name)
1132
Bryan Jacobsf609f912013-05-06 13:36:24 -04001133 if dest_branch is None:
1134 dest_branch = self.dest_branch
1135 if dest_branch is None:
1136 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001137 if not dest_branch.startswith(R_HEADS):
1138 dest_branch = R_HEADS + dest_branch
1139
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001140 if not branch.remote.projectname:
1141 branch.remote.projectname = self.name
1142 branch.remote.Save()
1143
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001144 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001145 if url is None:
1146 raise UploadError('review not configured')
1147 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001148
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001149 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001150 rp = ['gerrit receive-pack']
1151 for e in people[0]:
1152 rp.append('--reviewer=%s' % sq(e))
1153 for e in people[1]:
1154 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001155 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001156
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001157 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001158
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001159 if dest_branch.startswith(R_HEADS):
1160 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001161
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001162 upload_type = 'for'
1163 if draft:
1164 upload_type = 'drafts'
1165
1166 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1167 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001168 if auto_topic:
1169 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiao87984c62017-08-02 16:55:03 +02001170
Shawn Pearce45d21682013-02-28 00:35:51 -08001171 if not url.startswith('ssh://'):
1172 rp = ['r=%s' % p for p in people[0]] + \
1173 ['cc=%s' % p for p in people[1]]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001174 if private:
1175 rp = rp + ['private']
1176 if wip:
1177 rp = rp + ['wip']
Shawn Pearce45d21682013-02-28 00:35:51 -08001178 if rp:
1179 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001180 cmd.append(ref_spec)
1181
Anthony King7bdac712014-07-16 12:56:40 +01001182 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001183 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184
1185 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1186 self.bare_git.UpdateRef(R_PUB + branch.name,
1187 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001188 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
1190
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001191# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192
Julien Campergue335f5ef2013-10-16 11:02:35 +02001193 def _ExtractArchive(self, tarpath, path=None):
1194 """Extract the given tar on its current location
1195
1196 Args:
1197 - tarpath: The path to the actual tar file
1198
1199 """
1200 try:
1201 with tarfile.open(tarpath, 'r') as tar:
1202 tar.extractall(path=path)
1203 return True
1204 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001205 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001206 return False
1207
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001208 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001209 quiet=False,
1210 is_new=None,
1211 current_branch_only=False,
1212 force_sync=False,
1213 clone_bundle=True,
1214 no_tags=False,
1215 archive=False,
1216 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001217 prune=False,
1218 submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219 """Perform only the network IO portion of the sync process.
1220 Local working directory/branch state is not affected.
1221 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001222 if archive and not isinstance(self, MetaProject):
1223 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001224 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001225 return False
1226
1227 name = self.relpath.replace('\\', '/')
1228 name = name.replace('/', '_')
1229 tarpath = '%s.tar' % name
1230 topdir = self.manifest.topdir
1231
1232 try:
1233 self._FetchArchive(tarpath, cwd=topdir)
1234 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001235 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001236 return False
1237
1238 # From now on, we only need absolute tarpath
1239 tarpath = os.path.join(topdir, tarpath)
1240
1241 if not self._ExtractArchive(tarpath, path=topdir):
1242 return False
1243 try:
1244 os.remove(tarpath)
1245 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001246 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001247 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001248 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001249 if is_new is None:
1250 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001251 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001252 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001253 else:
1254 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001256
1257 if is_new:
1258 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1259 try:
1260 fd = open(alt, 'rb')
1261 try:
1262 alt_dir = fd.readline().rstrip()
1263 finally:
1264 fd.close()
1265 except IOError:
1266 alt_dir = None
1267 else:
1268 alt_dir = None
1269
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001270 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001271 and alt_dir is None \
1272 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001273 is_new = False
1274
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001275 if not current_branch_only:
1276 if self.sync_c:
1277 current_branch_only = True
1278 elif not self.manifest._loaded:
1279 # Manifest cannot check defaults until it syncs.
1280 current_branch_only = False
1281 elif self.manifest.default.sync_c:
1282 current_branch_only = True
1283
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001284 if self.clone_depth:
1285 depth = self.clone_depth
1286 else:
1287 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1288
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001289 need_to_fetch = not (optimized_fetch and
1290 (ID_RE.match(self.revisionExpr) and
1291 self._CheckForSha1()))
1292 if (need_to_fetch and
1293 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1294 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001295 no_tags=no_tags, prune=prune, depth=depth,
1296 submodules=submodules)):
Anthony King7bdac712014-07-16 12:56:40 +01001297 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001298
1299 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001300 self._InitMRef()
1301 else:
1302 self._InitMirrorHead()
1303 try:
1304 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1305 except OSError:
1306 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001308
1309 def PostRepoUpgrade(self):
1310 self._InitHooks()
1311
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001312 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001313 if self.manifest.isGitcClient:
1314 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001315 for copyfile in self.copyfiles:
1316 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001317 for linkfile in self.linkfiles:
1318 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319
Julien Camperguedd654222014-01-09 16:21:37 +01001320 def GetCommitRevisionId(self):
1321 """Get revisionId of a commit.
1322
1323 Use this method instead of GetRevisionId to get the id of the commit rather
1324 than the id of the current git object (for example, a tag)
1325
1326 """
1327 if not self.revisionExpr.startswith(R_TAGS):
1328 return self.GetRevisionId(self._allrefs)
1329
1330 try:
1331 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1332 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001333 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1334 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001335
David Pursehouse8a68ff92012-09-24 12:15:13 +09001336 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001337 if self.revisionId:
1338 return self.revisionId
1339
1340 rem = self.GetRemote(self.remote.name)
1341 rev = rem.ToLocal(self.revisionExpr)
1342
David Pursehouse8a68ff92012-09-24 12:15:13 +09001343 if all_refs is not None and rev in all_refs:
1344 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001345
1346 try:
1347 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1348 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001349 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1350 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351
Martin Kellye4e94d22017-03-21 16:05:12 -07001352 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 """Perform only the local IO portion of the sync process.
1354 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001356 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001357 all_refs = self.bare_ref.all
1358 self.CleanPublishedCache(all_refs)
1359 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001360
David Pursehouse1d947b32012-10-25 12:23:11 +09001361 def _doff():
1362 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001363 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001364
Martin Kellye4e94d22017-03-21 16:05:12 -07001365 def _dosubmodules():
1366 self._SyncSubmodules(quiet=True)
1367
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001368 head = self.work_git.GetHead()
1369 if head.startswith(R_HEADS):
1370 branch = head[len(R_HEADS):]
1371 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001372 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001373 except KeyError:
1374 head = None
1375 else:
1376 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001378 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 # Currently on a detached HEAD. The user is assumed to
1380 # not have any local modifications worth worrying about.
1381 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001382 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001383 syncbuf.fail(self, _PriorSyncFailedError())
1384 return
1385
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001386 if head == revid:
1387 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001388 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001389 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001390 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001391 # The copy/linkfile config may have changed.
1392 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001393 return
1394 else:
1395 lost = self._revlist(not_rev(revid), HEAD)
1396 if lost:
1397 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001398
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001400 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001401 if submodules:
1402 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001403 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001404 syncbuf.fail(self, e)
1405 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001406 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001407 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001409 if head == revid:
1410 # No changes; don't do anything further.
1411 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001412 # The copy/linkfile config may have changed.
1413 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001414 return
1415
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001416 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001417
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001418 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001420 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001422 syncbuf.info(self,
1423 "leaving %s; does not track upstream",
1424 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001426 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001427 if submodules:
1428 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001429 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001430 syncbuf.fail(self, e)
1431 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001432 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001433 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001435 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001436 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001437 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001438 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001439 if not_merged:
1440 if upstream_gain:
1441 # The user has published this branch and some of those
1442 # commits are not yet merged upstream. We do not want
1443 # to rewrite the published commits so we punt.
1444 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001445 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001446 "branch %s is published (but not merged) and is now "
1447 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001448 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001449 elif pub == head:
1450 # All published commits are merged, and thus we are a
1451 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001452 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001453 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001454 if submodules:
1455 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001456 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001458 # Examine the local commits not in the remote. Find the
1459 # last one attributed to this user, if any.
1460 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001461 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001462 last_mine = None
1463 cnt_mine = 0
1464 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301465 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001466 if committer_email == self.UserEmail:
1467 last_mine = commit_id
1468 cnt_mine += 1
1469
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001470 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001471 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472
1473 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001474 syncbuf.fail(self, _DirtyError())
1475 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001476
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001477 # If the upstream switched on us, warn the user.
1478 #
1479 if branch.merge != self.revisionExpr:
1480 if branch.merge and self.revisionExpr:
1481 syncbuf.info(self,
1482 'manifest switched %s...%s',
1483 branch.merge,
1484 self.revisionExpr)
1485 elif branch.merge:
1486 syncbuf.info(self,
1487 'manifest no longer tracks %s',
1488 branch.merge)
1489
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001490 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001492 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001493 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001494 syncbuf.info(self,
1495 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001496 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001497
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001498 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001499 if not ID_RE.match(self.revisionExpr):
1500 # in case of manifest sync the revisionExpr might be a SHA1
1501 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001502 if not branch.merge.startswith('refs/'):
1503 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504 branch.Save()
1505
Mike Pontillod3153822012-02-28 11:53:24 -08001506 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001507 def _docopyandlink():
1508 self._CopyAndLinkFiles()
1509
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001510 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001511 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001512 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001513 if submodules:
1514 syncbuf.later2(self, _dosubmodules)
1515 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001516 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001518 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001519 if submodules:
1520 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001521 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001522 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001523 syncbuf.fail(self, e)
1524 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001525 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001526 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001527 if submodules:
1528 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001529
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001530 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001531 # dest should already be an absolute path, but src is project relative
1532 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001533 abssrc = os.path.join(self.worktree, src)
1534 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001535
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001536 def AddLinkFile(self, src, dest, absdest):
1537 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001538 # make src relative path to dest
1539 absdestdir = os.path.dirname(absdest)
1540 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001541 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001542
James W. Mills24c13082012-04-12 15:04:13 -05001543 def AddAnnotation(self, name, value, keep):
1544 self.annotations.append(_Annotation(name, value, keep))
1545
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001546 def DownloadPatchSet(self, change_id, patch_id):
1547 """Download a single patch set of a single change to FETCH_HEAD.
1548 """
1549 remote = self.GetRemote(self.remote.name)
1550
1551 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001552 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001553 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001554 if GitCommand(self, cmd, bare=True).Wait() != 0:
1555 return None
1556 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001557 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001558 change_id,
1559 patch_id,
1560 self.bare_git.rev_parse('FETCH_HEAD'))
1561
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001562
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001563# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564
Simran Basib9a1b732015-08-20 12:19:28 -07001565 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566 """Create a new branch off the manifest's revision.
1567 """
Simran Basib9a1b732015-08-20 12:19:28 -07001568 if not branch_merge:
1569 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001570 head = self.work_git.GetHead()
1571 if head == (R_HEADS + name):
1572 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573
David Pursehouse8a68ff92012-09-24 12:15:13 +09001574 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001575 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001576 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001577 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001578 capture_stdout=True,
1579 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001580
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001581 branch = self.GetBranch(name)
1582 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001583 branch.merge = branch_merge
1584 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1585 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001586 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001587
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001588 if head.startswith(R_HEADS):
1589 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001590 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591 except KeyError:
1592 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001593 if revid and head and revid == head:
1594 ref = os.path.join(self.gitdir, R_HEADS + name)
1595 try:
1596 os.makedirs(os.path.dirname(ref))
1597 except OSError:
1598 pass
1599 _lwrite(ref, '%s\n' % revid)
1600 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1601 'ref: %s%s\n' % (R_HEADS, name))
1602 branch.Save()
1603 return True
1604
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001605 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001606 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001607 capture_stdout=True,
1608 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001609 branch.Save()
1610 return True
1611 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001612
Wink Saville02d79452009-04-10 13:01:24 -07001613 def CheckoutBranch(self, name):
1614 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001615
1616 Args:
1617 name: The name of the branch to checkout.
1618
1619 Returns:
1620 True if the checkout succeeded; False if it didn't; None if the branch
1621 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001622 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001623 rev = R_HEADS + name
1624 head = self.work_git.GetHead()
1625 if head == rev:
1626 # Already on the branch
1627 #
1628 return True
Wink Saville02d79452009-04-10 13:01:24 -07001629
David Pursehouse8a68ff92012-09-24 12:15:13 +09001630 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001631 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001632 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001633 except KeyError:
1634 # Branch does not exist in this project
1635 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001636 return None
Wink Saville02d79452009-04-10 13:01:24 -07001637
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001638 if head.startswith(R_HEADS):
1639 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001640 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001641 except KeyError:
1642 head = None
1643
1644 if head == revid:
1645 # Same revision; just update HEAD to point to the new
1646 # target branch, but otherwise take no other action.
1647 #
1648 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1649 'ref: %s%s\n' % (R_HEADS, name))
1650 return True
1651
1652 return GitCommand(self,
1653 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001654 capture_stdout=True,
1655 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001656
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001657 def AbandonBranch(self, name):
1658 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001659
1660 Args:
1661 name: The name of the branch to abandon.
1662
1663 Returns:
1664 True if the abandon succeeded; False if it didn't; None if the branch
1665 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001666 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001667 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001668 all_refs = self.bare_ref.all
1669 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001670 # Doesn't exist
1671 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001672
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001673 head = self.work_git.GetHead()
1674 if head == rev:
1675 # We can't destroy the branch while we are sitting
1676 # on it. Switch to a detached HEAD.
1677 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001678 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001679
David Pursehouse8a68ff92012-09-24 12:15:13 +09001680 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001681 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001682 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1683 '%s\n' % revid)
1684 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001685 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001686
1687 return GitCommand(self,
1688 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001689 capture_stdout=True,
1690 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001691
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 def PruneHeads(self):
1693 """Prune any topic branches already merged into upstream.
1694 """
1695 cb = self.CurrentBranch
1696 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001697 left = self._allrefs
1698 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699 if name.startswith(R_HEADS):
1700 name = name[len(R_HEADS):]
1701 if cb is None or name != cb:
1702 kill.append(name)
1703
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001704 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 if cb is not None \
1706 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001707 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708 self.work_git.DetachHead(HEAD)
1709 kill.append(cb)
1710
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001711 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001712 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001714 try:
1715 self.bare_git.DetachHead(rev)
1716
1717 b = ['branch', '-d']
1718 b.extend(kill)
1719 b = GitCommand(self, b, bare=True,
1720 capture_stdout=True,
1721 capture_stderr=True)
1722 b.Wait()
1723 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001724 if ID_RE.match(old):
1725 self.bare_git.DetachHead(old)
1726 else:
1727 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001728 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001730 for branch in kill:
1731 if (R_HEADS + branch) not in left:
1732 self.CleanPublishedCache()
1733 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734
1735 if cb and cb not in kill:
1736 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001737 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738
1739 kept = []
1740 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001741 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742 branch = self.GetBranch(branch)
1743 base = branch.LocalMerge
1744 if not base:
1745 base = rev
1746 kept.append(ReviewableBranch(self, branch, base))
1747 return kept
1748
1749
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001750# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001751
1752 def GetRegisteredSubprojects(self):
1753 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001754
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001755 def rec(subprojects):
1756 if not subprojects:
1757 return
1758 result.extend(subprojects)
1759 for p in subprojects:
1760 rec(p.subprojects)
1761 rec(self.subprojects)
1762 return result
1763
1764 def _GetSubmodules(self):
1765 # Unfortunately we cannot call `git submodule status --recursive` here
1766 # because the working tree might not exist yet, and it cannot be used
1767 # without a working tree in its current implementation.
1768
1769 def get_submodules(gitdir, rev):
1770 # Parse .gitmodules for submodule sub_paths and sub_urls
1771 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1772 if not sub_paths:
1773 return []
1774 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1775 # revision of submodule repository
1776 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1777 submodules = []
1778 for sub_path, sub_url in zip(sub_paths, sub_urls):
1779 try:
1780 sub_rev = sub_revs[sub_path]
1781 except KeyError:
1782 # Ignore non-exist submodules
1783 continue
1784 submodules.append((sub_rev, sub_path, sub_url))
1785 return submodules
1786
1787 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1788 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001789
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001790 def parse_gitmodules(gitdir, rev):
1791 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1792 try:
Anthony King7bdac712014-07-16 12:56:40 +01001793 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1794 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001795 except GitError:
1796 return [], []
1797 if p.Wait() != 0:
1798 return [], []
1799
1800 gitmodules_lines = []
1801 fd, temp_gitmodules_path = tempfile.mkstemp()
1802 try:
1803 os.write(fd, p.stdout)
1804 os.close(fd)
1805 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001806 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1807 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001808 if p.Wait() != 0:
1809 return [], []
1810 gitmodules_lines = p.stdout.split('\n')
1811 except GitError:
1812 return [], []
1813 finally:
1814 os.remove(temp_gitmodules_path)
1815
1816 names = set()
1817 paths = {}
1818 urls = {}
1819 for line in gitmodules_lines:
1820 if not line:
1821 continue
1822 m = re_path.match(line)
1823 if m:
1824 names.add(m.group(1))
1825 paths[m.group(1)] = m.group(2)
1826 continue
1827 m = re_url.match(line)
1828 if m:
1829 names.add(m.group(1))
1830 urls[m.group(1)] = m.group(2)
1831 continue
1832 names = sorted(names)
1833 return ([paths.get(name, '') for name in names],
1834 [urls.get(name, '') for name in names])
1835
1836 def git_ls_tree(gitdir, rev, paths):
1837 cmd = ['ls-tree', rev, '--']
1838 cmd.extend(paths)
1839 try:
Anthony King7bdac712014-07-16 12:56:40 +01001840 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1841 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001842 except GitError:
1843 return []
1844 if p.Wait() != 0:
1845 return []
1846 objects = {}
1847 for line in p.stdout.split('\n'):
1848 if not line.strip():
1849 continue
1850 object_rev, object_path = line.split()[2:4]
1851 objects[object_path] = object_rev
1852 return objects
1853
1854 try:
1855 rev = self.GetRevisionId()
1856 except GitError:
1857 return []
1858 return get_submodules(self.gitdir, rev)
1859
1860 def GetDerivedSubprojects(self):
1861 result = []
1862 if not self.Exists:
1863 # If git repo does not exist yet, querying its submodules will
1864 # mess up its states; so return here.
1865 return result
1866 for rev, path, url in self._GetSubmodules():
1867 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001868 relpath, worktree, gitdir, objdir = \
1869 self.manifest.GetSubprojectPaths(self, name, path)
1870 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001871 if project:
1872 result.extend(project.GetDerivedSubprojects())
1873 continue
David James8d201162013-10-11 17:03:19 -07001874
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001875 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001876 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001877 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001878 review=self.remote.review,
1879 revision=self.remote.revision)
1880 subproject = Project(manifest=self.manifest,
1881 name=name,
1882 remote=remote,
1883 gitdir=gitdir,
1884 objdir=objdir,
1885 worktree=worktree,
1886 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001887 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001888 revisionId=rev,
1889 rebase=self.rebase,
1890 groups=self.groups,
1891 sync_c=self.sync_c,
1892 sync_s=self.sync_s,
1893 parent=self,
1894 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001895 result.append(subproject)
1896 result.extend(subproject.GetDerivedSubprojects())
1897 return result
1898
1899
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001900# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001901 def _CheckForSha1(self):
1902 try:
1903 # if revision (sha or tag) is not present then following function
1904 # throws an error.
1905 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1906 return True
1907 except GitError:
1908 # There is no such persistent revision. We have to fetch it.
1909 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001910
Julien Campergue335f5ef2013-10-16 11:02:35 +02001911 def _FetchArchive(self, tarpath, cwd=None):
1912 cmd = ['archive', '-v', '-o', tarpath]
1913 cmd.append('--remote=%s' % self.remote.url)
1914 cmd.append('--prefix=%s/' % self.relpath)
1915 cmd.append(self.revisionExpr)
1916
1917 command = GitCommand(self, cmd, cwd=cwd,
1918 capture_stdout=True,
1919 capture_stderr=True)
1920
1921 if command.Wait() != 0:
1922 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1923
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001924 def _RemoteFetch(self, name=None,
1925 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001926 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001927 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001928 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001929 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001930 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001931 depth=None,
1932 submodules=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001933
1934 is_sha1 = False
1935 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001936 # The depth should not be used when fetching to a mirror because
1937 # it will result in a shallow repository that cannot be cloned or
1938 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001939 # The repo project should also never be synced with partial depth.
1940 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1941 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001942
Shawn Pearce69e04d82014-01-29 12:48:54 -08001943 if depth:
1944 current_branch_only = True
1945
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001946 if ID_RE.match(self.revisionExpr) is not None:
1947 is_sha1 = True
1948
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001949 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001950 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001951 # this is a tag and its sha1 value should never change
1952 tag_name = self.revisionExpr[len(R_TAGS):]
1953
1954 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001955 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001956 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001957 if is_sha1 and not depth:
1958 # When syncing a specific commit and --depth is not set:
1959 # * if upstream is explicitly specified and is not a sha1, fetch only
1960 # upstream as users expect only upstream to be fetch.
1961 # Note: The commit might not be in upstream in which case the sync
1962 # will fail.
1963 # * otherwise, fetch all branches to make sure we end up with the
1964 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001965 if self.upstream:
1966 current_branch_only = not ID_RE.match(self.upstream)
1967 else:
1968 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001969
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970 if not name:
1971 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001972
1973 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001974 remote = self.GetRemote(name)
1975 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001976 ssh_proxy = True
1977
Shawn O. Pearce88443382010-10-08 10:02:09 +02001978 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001979 if alt_dir and 'objects' == os.path.basename(alt_dir):
1980 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001981 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1982 remote = self.GetRemote(name)
1983
David Pursehouse8a68ff92012-09-24 12:15:13 +09001984 all_refs = self.bare_ref.all
1985 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001986 tmp = set()
1987
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301988 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001989 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001990 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001991 all_refs[r] = ref_id
1992 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001993 continue
1994
David Pursehouse8a68ff92012-09-24 12:15:13 +09001995 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001996 continue
1997
David Pursehouse8a68ff92012-09-24 12:15:13 +09001998 r = 'refs/_alt/%s' % ref_id
1999 all_refs[r] = ref_id
2000 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002001 tmp.add(r)
2002
heping3d7bbc92017-04-12 19:51:47 +08002003 tmp_packed_lines = []
2004 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002005
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302006 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002007 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002008 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002009 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002010 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002011
heping3d7bbc92017-04-12 19:51:47 +08002012 tmp_packed = ''.join(tmp_packed_lines)
2013 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002014 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002015 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002016 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002017
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002018 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002019
Conley Owensf97e8382015-01-21 11:12:46 -08002020 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002021 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002022 else:
2023 # If this repo has shallow objects, then we don't know which refs have
2024 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2025 # do this with projects that don't have shallow objects, since it is less
2026 # efficient.
2027 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2028 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002029
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002030 if quiet:
2031 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002032 if not self.worktree:
2033 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002034 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002035
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002036 # If using depth then we should not get all the tags since they may
2037 # be outside of the depth.
2038 if no_tags or depth:
2039 cmd.append('--no-tags')
2040 else:
2041 cmd.append('--tags')
2042
David Pursehouse74cfd272015-10-14 10:50:15 +09002043 if prune:
2044 cmd.append('--prune')
2045
Martin Kellye4e94d22017-03-21 16:05:12 -07002046 if submodules:
2047 cmd.append('--recurse-submodules=on-demand')
2048
Conley Owens80b87fe2014-05-09 17:13:44 -07002049 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002050 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002051 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002052 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002053 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002054 spec.append('tag')
2055 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002056
David Pursehouse403b64e2015-04-27 10:41:33 +09002057 if not self.manifest.IsMirror:
2058 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002059 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002060 # Shallow checkout of a specific commit, fetch from that commit and not
2061 # the heads only as the commit might be deeper in the history.
2062 spec.append(branch)
2063 else:
2064 if is_sha1:
2065 branch = self.upstream
2066 if branch is not None and branch.strip():
2067 if not branch.startswith('refs/'):
2068 branch = R_HEADS + branch
2069 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002070 cmd.extend(spec)
2071
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002072 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002073 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002074 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002075 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002076 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002077 ok = True
2078 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002079 # If needed, run the 'git remote prune' the first time through the loop
2080 elif (not _i and
2081 "error:" in gitcmd.stderr and
2082 "git remote prune" in gitcmd.stderr):
2083 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002084 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002085 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002086 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002087 break
2088 continue
Brian Harring14a66742012-09-28 20:21:57 -07002089 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002090 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2091 # in sha1 mode, we just tried sync'ing from the upstream field; it
2092 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002093 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002094 elif ret < 0:
2095 # Git died with a signal, exit immediately
2096 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002097 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002098
2099 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002100 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002101 if old_packed != '':
2102 _lwrite(packed_refs, old_packed)
2103 else:
2104 os.remove(packed_refs)
2105 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002106
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002107 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002108 # We just synced the upstream given branch; verify we
2109 # got what we wanted, else trigger a second run of all
2110 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002111 if not self._CheckForSha1():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002112 if current_branch_only and depth:
2113 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002114 return self._RemoteFetch(name=name,
2115 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002116 initial=False, quiet=quiet, alt_dir=alt_dir,
2117 depth=None)
2118 else:
2119 # Avoid infinite recursion: sync all branches with depth set to None
2120 return self._RemoteFetch(name=name, current_branch_only=False,
2121 initial=False, quiet=quiet, alt_dir=alt_dir,
2122 depth=None)
Brian Harring14a66742012-09-28 20:21:57 -07002123
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002124 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002125
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002126 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002127 if initial and \
2128 (self.manifest.manifestProject.config.GetString('repo.depth') or
2129 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002130 return False
2131
2132 remote = self.GetRemote(self.remote.name)
2133 bundle_url = remote.url + '/clone.bundle'
2134 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002135 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2136 'persistent-http',
2137 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002138 return False
2139
2140 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2141 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2142
2143 exist_dst = os.path.exists(bundle_dst)
2144 exist_tmp = os.path.exists(bundle_tmp)
2145
2146 if not initial and not exist_dst and not exist_tmp:
2147 return False
2148
2149 if not exist_dst:
2150 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2151 if not exist_dst:
2152 return False
2153
2154 cmd = ['fetch']
2155 if quiet:
2156 cmd.append('--quiet')
2157 if not self.worktree:
2158 cmd.append('--update-head-ok')
2159 cmd.append(bundle_dst)
2160 for f in remote.fetch:
2161 cmd.append(str(f))
2162 cmd.append('refs/tags/*:refs/tags/*')
2163
2164 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002165 if os.path.exists(bundle_dst):
2166 os.remove(bundle_dst)
2167 if os.path.exists(bundle_tmp):
2168 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002170
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002171 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002172 if os.path.exists(dstPath):
2173 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002174
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002175 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002176 if quiet:
2177 cmd += ['--silent']
2178 if os.path.exists(tmpPath):
2179 size = os.stat(tmpPath).st_size
2180 if size >= 1024:
2181 cmd += ['--continue-at', '%d' % (size,)]
2182 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002183 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002184 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2185 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002186 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002187 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002188 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002189 if srcUrl.startswith('persistent-'):
2190 srcUrl = srcUrl[len('persistent-'):]
2191 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002192
Dave Borowitz137d0132015-01-02 11:12:54 -08002193 if IsTrace():
2194 Trace('%s', ' '.join(cmd))
2195 try:
2196 proc = subprocess.Popen(cmd)
2197 except OSError:
2198 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002199
Dave Borowitz137d0132015-01-02 11:12:54 -08002200 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002201
Dave Borowitz137d0132015-01-02 11:12:54 -08002202 if curlret == 22:
2203 # From curl man page:
2204 # 22: HTTP page not retrieved. The requested url was not found or
2205 # returned another error with the HTTP error code being 400 or above.
2206 # This return code only appears if -f, --fail is used.
2207 if not quiet:
2208 print("Server does not provide clone.bundle; ignoring.",
2209 file=sys.stderr)
2210 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002211
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002212 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002213 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002214 os.rename(tmpPath, dstPath)
2215 return True
2216 else:
2217 os.remove(tmpPath)
2218 return False
2219 else:
2220 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002221
Kris Giesingc8d882a2014-12-23 13:02:32 -08002222 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002223 try:
2224 with open(path) as f:
2225 if f.read(16) == '# v2 git bundle\n':
2226 return True
2227 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002228 if not quiet:
2229 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002230 return False
2231 except OSError:
2232 return False
2233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002234 def _Checkout(self, rev, quiet=False):
2235 cmd = ['checkout']
2236 if quiet:
2237 cmd.append('-q')
2238 cmd.append(rev)
2239 cmd.append('--')
2240 if GitCommand(self, cmd).Wait() != 0:
2241 if self._allrefs:
2242 raise GitError('%s checkout %s ' % (self.name, rev))
2243
Anthony King7bdac712014-07-16 12:56:40 +01002244 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002245 cmd = ['cherry-pick']
2246 cmd.append(rev)
2247 cmd.append('--')
2248 if GitCommand(self, cmd).Wait() != 0:
2249 if self._allrefs:
2250 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2251
Anthony King7bdac712014-07-16 12:56:40 +01002252 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002253 cmd = ['revert']
2254 cmd.append('--no-edit')
2255 cmd.append(rev)
2256 cmd.append('--')
2257 if GitCommand(self, cmd).Wait() != 0:
2258 if self._allrefs:
2259 raise GitError('%s revert %s ' % (self.name, rev))
2260
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002261 def _ResetHard(self, rev, quiet=True):
2262 cmd = ['reset', '--hard']
2263 if quiet:
2264 cmd.append('-q')
2265 cmd.append(rev)
2266 if GitCommand(self, cmd).Wait() != 0:
2267 raise GitError('%s reset --hard %s ' % (self.name, rev))
2268
Martin Kellye4e94d22017-03-21 16:05:12 -07002269 def _SyncSubmodules(self, quiet=True):
2270 cmd = ['submodule', 'update', '--init', '--recursive']
2271 if quiet:
2272 cmd.append('-q')
2273 if GitCommand(self, cmd).Wait() != 0:
2274 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2275
Anthony King7bdac712014-07-16 12:56:40 +01002276 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002277 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002278 if onto is not None:
2279 cmd.extend(['--onto', onto])
2280 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002281 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002282 raise GitError('%s rebase %s ' % (self.name, upstream))
2283
Pierre Tardy3d125942012-05-04 12:18:12 +02002284 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002285 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002286 if ffonly:
2287 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 if GitCommand(self, cmd).Wait() != 0:
2289 raise GitError('%s merge %s ' % (self.name, head))
2290
Kevin Degiabaa7f32014-11-12 11:27:45 -07002291 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002292 init_git_dir = not os.path.exists(self.gitdir)
2293 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002294 try:
2295 # Initialize the bare repository, which contains all of the objects.
2296 if init_obj_dir:
2297 os.makedirs(self.objdir)
2298 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002299
Kevin Degib1a07b82015-07-27 13:33:43 -06002300 # If we have a separate directory to hold refs, initialize it as well.
2301 if self.objdir != self.gitdir:
2302 if init_git_dir:
2303 os.makedirs(self.gitdir)
2304
2305 if init_obj_dir or init_git_dir:
2306 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2307 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002308 try:
2309 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2310 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002311 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002312 print("Retrying clone after deleting %s" %
2313 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002314 try:
2315 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002316 if self.worktree and os.path.exists(os.path.realpath
2317 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002318 shutil.rmtree(os.path.realpath(self.worktree))
2319 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2320 except:
2321 raise e
2322 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002323
Kevin Degi384b3c52014-10-16 16:02:58 -06002324 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002325 mp = self.manifest.manifestProject
2326 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002327
Kevin Degib1a07b82015-07-27 13:33:43 -06002328 if ref_dir or mirror_git:
2329 if not mirror_git:
2330 mirror_git = os.path.join(ref_dir, self.name + '.git')
2331 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2332 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002333
Kevin Degib1a07b82015-07-27 13:33:43 -06002334 if os.path.exists(mirror_git):
2335 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002336
Kevin Degib1a07b82015-07-27 13:33:43 -06002337 elif os.path.exists(repo_git):
2338 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002339
Kevin Degib1a07b82015-07-27 13:33:43 -06002340 else:
2341 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002342
Kevin Degib1a07b82015-07-27 13:33:43 -06002343 if ref_dir:
2344 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2345 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002346
Kevin Degib1a07b82015-07-27 13:33:43 -06002347 self._UpdateHooks()
2348
2349 m = self.manifest.manifestProject.config
2350 for key in ['user.name', 'user.email']:
2351 if m.Has(key, include_defaults=False):
2352 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002353 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002354 if self.manifest.IsMirror:
2355 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002356 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002357 self.config.SetString('core.bare', None)
2358 except Exception:
2359 if init_obj_dir and os.path.exists(self.objdir):
2360 shutil.rmtree(self.objdir)
2361 if init_git_dir and os.path.exists(self.gitdir):
2362 shutil.rmtree(self.gitdir)
2363 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002364
Jimmie Westera0444582012-10-24 13:44:42 +02002365 def _UpdateHooks(self):
2366 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002367 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002368
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002369 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002370 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002371 if not os.path.exists(hooks):
2372 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002373 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002374 name = os.path.basename(stock_hook)
2375
Victor Boivie65e0f352011-04-18 11:23:29 +02002376 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002377 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002378 # Don't install a Gerrit Code Review hook if this
2379 # project does not appear to use it for reviews.
2380 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002381 # Since the manifest project is one of those, but also
2382 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002383 continue
2384
2385 dst = os.path.join(hooks, name)
2386 if os.path.islink(dst):
2387 continue
2388 if os.path.exists(dst):
2389 if filecmp.cmp(stock_hook, dst, shallow=False):
2390 os.remove(dst)
2391 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002392 _warn("%s: Not replacing locally modified %s hook",
2393 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002394 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002395 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002396 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002397 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002398 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002399 raise GitError('filesystem must support symlinks')
2400 else:
2401 raise
2402
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002404 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002405 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002406 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002407 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002408 remote.review = self.remote.review
2409 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002410
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002411 if self.worktree:
2412 remote.ResetFetch(mirror=False)
2413 else:
2414 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002415 remote.Save()
2416
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002417 def _InitMRef(self):
2418 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002419 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002420
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002421 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002422 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002423
2424 def _InitAnyMRef(self, ref):
2425 cur = self.bare_ref.symref(ref)
2426
2427 if self.revisionId:
2428 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2429 msg = 'manifest set to %s' % self.revisionId
2430 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002431 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002432 else:
2433 remote = self.GetRemote(self.remote.name)
2434 dst = remote.ToLocal(self.revisionExpr)
2435 if cur != dst:
2436 msg = 'manifest set to %s' % self.revisionExpr
2437 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002438
Kevin Degi384b3c52014-10-16 16:02:58 -06002439 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002440 symlink_files = self.shareable_files[:]
2441 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002442 if share_refs:
2443 symlink_files += self.working_tree_files
2444 symlink_dirs += self.working_tree_dirs
2445 to_symlink = symlink_files + symlink_dirs
2446 for name in set(to_symlink):
2447 dst = os.path.realpath(os.path.join(destdir, name))
2448 if os.path.lexists(dst):
2449 src = os.path.realpath(os.path.join(srcdir, name))
2450 # Fail if the links are pointing to the wrong place
2451 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002452 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002453 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002454 'work tree. If you\'re comfortable with the '
2455 'possibility of losing the work tree\'s git metadata,'
2456 ' use `repo sync --force-sync {0}` to '
2457 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002458
David James8d201162013-10-11 17:03:19 -07002459 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2460 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2461
2462 Args:
2463 gitdir: The bare git repository. Must already be initialized.
2464 dotgit: The repository you would like to initialize.
2465 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2466 Only one work tree can store refs under a given |gitdir|.
2467 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2468 This saves you the effort of initializing |dotgit| yourself.
2469 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002470 symlink_files = self.shareable_files[:]
2471 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002472 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002473 symlink_files += self.working_tree_files
2474 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002475 to_symlink = symlink_files + symlink_dirs
2476
2477 to_copy = []
2478 if copy_all:
2479 to_copy = os.listdir(gitdir)
2480
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002481 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002482 for name in set(to_copy).union(to_symlink):
2483 try:
2484 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002485 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002486
Kevin Degi384b3c52014-10-16 16:02:58 -06002487 if os.path.lexists(dst):
2488 continue
David James8d201162013-10-11 17:03:19 -07002489
2490 # If the source dir doesn't exist, create an empty dir.
2491 if name in symlink_dirs and not os.path.lexists(src):
2492 os.makedirs(src)
2493
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002494 if name in to_symlink:
2495 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2496 elif copy_all and not os.path.islink(dst):
2497 if os.path.isdir(src):
2498 shutil.copytree(src, dst)
2499 elif os.path.isfile(src):
2500 shutil.copy(src, dst)
2501
Conley Owens80b87fe2014-05-09 17:13:44 -07002502 # If the source file doesn't exist, ensure the destination
2503 # file doesn't either.
2504 if name in symlink_files and not os.path.lexists(src):
2505 try:
2506 os.remove(dst)
2507 except OSError:
2508 pass
2509
David James8d201162013-10-11 17:03:19 -07002510 except OSError as e:
2511 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002512 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002513 else:
2514 raise
2515
Martin Kellye4e94d22017-03-21 16:05:12 -07002516 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002517 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002518 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002519 try:
2520 if init_dotgit:
2521 os.makedirs(dotgit)
2522 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2523 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002524
Kevin Degiabaa7f32014-11-12 11:27:45 -07002525 try:
2526 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2527 except GitError as e:
2528 if force_sync:
2529 try:
2530 shutil.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002531 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002532 except:
2533 raise e
2534 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002535
Kevin Degib1a07b82015-07-27 13:33:43 -06002536 if init_dotgit:
2537 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002538
Kevin Degib1a07b82015-07-27 13:33:43 -06002539 cmd = ['read-tree', '--reset', '-u']
2540 cmd.append('-v')
2541 cmd.append(HEAD)
2542 if GitCommand(self, cmd).Wait() != 0:
2543 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002544
Martin Kellye4e94d22017-03-21 16:05:12 -07002545 if submodules:
2546 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002547 self._CopyAndLinkFiles()
2548 except Exception:
2549 if init_dotgit:
2550 shutil.rmtree(dotgit)
2551 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552
2553 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002554 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002556 def _revlist(self, *args, **kw):
2557 a = []
2558 a.extend(args)
2559 a.append('--')
2560 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002561
2562 @property
2563 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002564 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002565
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002566 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002567 """Get logs between two revisions of this project."""
2568 comp = '..'
2569 if rev1:
2570 revs = [rev1]
2571 if rev2:
2572 revs.extend([comp, rev2])
2573 cmd = ['log', ''.join(revs)]
2574 out = DiffColoring(self.config)
2575 if out.is_on and color:
2576 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002577 if pretty_format is not None:
2578 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002579 if oneline:
2580 cmd.append('--oneline')
2581
2582 try:
2583 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2584 if log.Wait() == 0:
2585 return log.stdout
2586 except GitError:
2587 # worktree may not exist if groups changed for example. In that case,
2588 # try in gitdir instead.
2589 if not os.path.exists(self.worktree):
2590 return self.bare_git.log(*cmd[1:])
2591 else:
2592 raise
2593 return None
2594
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002595 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2596 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002597 """Get the list of logs from this revision to given revisionId"""
2598 logs = {}
2599 selfId = self.GetRevisionId(self._allrefs)
2600 toId = toProject.GetRevisionId(toProject._allrefs)
2601
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002602 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2603 pretty_format=pretty_format)
2604 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2605 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002606 return logs
2607
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002609
David James8d201162013-10-11 17:03:19 -07002610 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 self._project = project
2612 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002613 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615 def LsOthers(self):
2616 p = GitCommand(self._project,
2617 ['ls-files',
2618 '-z',
2619 '--others',
2620 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002621 bare=False,
David James8d201162013-10-11 17:03:19 -07002622 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002623 capture_stdout=True,
2624 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002625 if p.Wait() == 0:
2626 out = p.stdout
2627 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002628 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002629 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630 return []
2631
2632 def DiffZ(self, name, *args):
2633 cmd = [name]
2634 cmd.append('-z')
2635 cmd.extend(args)
2636 p = GitCommand(self._project,
2637 cmd,
David James8d201162013-10-11 17:03:19 -07002638 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002639 bare=False,
2640 capture_stdout=True,
2641 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 try:
2643 out = p.process.stdout.read()
2644 r = {}
2645 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002646 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002648 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002649 info = next(out)
2650 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002651 except StopIteration:
2652 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002653
2654 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002655
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 def __init__(self, path, omode, nmode, oid, nid, state):
2657 self.path = path
2658 self.src_path = None
2659 self.old_mode = omode
2660 self.new_mode = nmode
2661 self.old_id = oid
2662 self.new_id = nid
2663
2664 if len(state) == 1:
2665 self.status = state
2666 self.level = None
2667 else:
2668 self.status = state[:1]
2669 self.level = state[1:]
2670 while self.level.startswith('0'):
2671 self.level = self.level[1:]
2672
2673 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002674 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675 if info.status in ('R', 'C'):
2676 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002677 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678 r[info.path] = info
2679 return r
2680 finally:
2681 p.Wait()
2682
2683 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002684 if self._bare:
2685 path = os.path.join(self._project.gitdir, HEAD)
2686 else:
2687 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002688 try:
2689 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002690 except IOError as e:
2691 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002692 try:
2693 line = fd.read()
2694 finally:
2695 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302696 try:
2697 line = line.decode()
2698 except AttributeError:
2699 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002700 if line.startswith('ref: '):
2701 return line[5:-1]
2702 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002703
2704 def SetHead(self, ref, message=None):
2705 cmdv = []
2706 if message is not None:
2707 cmdv.extend(['-m', message])
2708 cmdv.append(HEAD)
2709 cmdv.append(ref)
2710 self.symbolic_ref(*cmdv)
2711
2712 def DetachHead(self, new, message=None):
2713 cmdv = ['--no-deref']
2714 if message is not None:
2715 cmdv.extend(['-m', message])
2716 cmdv.append(HEAD)
2717 cmdv.append(new)
2718 self.update_ref(*cmdv)
2719
2720 def UpdateRef(self, name, new, old=None,
2721 message=None,
2722 detach=False):
2723 cmdv = []
2724 if message is not None:
2725 cmdv.extend(['-m', message])
2726 if detach:
2727 cmdv.append('--no-deref')
2728 cmdv.append(name)
2729 cmdv.append(new)
2730 if old is not None:
2731 cmdv.append(old)
2732 self.update_ref(*cmdv)
2733
2734 def DeleteRef(self, name, old=None):
2735 if not old:
2736 old = self.rev_parse(name)
2737 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002738 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002739
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002740 def rev_list(self, *args, **kw):
2741 if 'format' in kw:
2742 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2743 else:
2744 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002745 cmdv.extend(args)
2746 p = GitCommand(self._project,
2747 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002748 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002749 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002750 capture_stdout=True,
2751 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002752 r = []
2753 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002754 if line[-1] == '\n':
2755 line = line[:-1]
2756 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002757 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002758 raise GitError('%s rev-list %s: %s' %
2759 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002760 return r
2761
2762 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002763 """Allow arbitrary git commands using pythonic syntax.
2764
2765 This allows you to do things like:
2766 git_obj.rev_parse('HEAD')
2767
2768 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2769 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002770 Any other positional arguments will be passed to the git command, and the
2771 following keyword arguments are supported:
2772 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002773
2774 Args:
2775 name: The name of the git command to call. Any '_' characters will
2776 be replaced with '-'.
2777
2778 Returns:
2779 A callable object that will try to call git with the named command.
2780 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002781 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002782
Dave Borowitz091f8932012-10-23 17:01:04 -07002783 def runner(*args, **kwargs):
2784 cmdv = []
2785 config = kwargs.pop('config', None)
2786 for k in kwargs:
2787 raise TypeError('%s() got an unexpected keyword argument %r'
2788 % (name, k))
2789 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002790 if not git_require((1, 7, 2)):
2791 raise ValueError('cannot set config on command line for %s()'
2792 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302793 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002794 cmdv.append('-c')
2795 cmdv.append('%s=%s' % (k, v))
2796 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002797 cmdv.extend(args)
2798 p = GitCommand(self._project,
2799 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002800 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002801 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002802 capture_stdout=True,
2803 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002804 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002805 raise GitError('%s %s: %s' %
2806 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002807 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302808 try:
Conley Owensedd01512013-09-26 12:59:58 -07002809 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302810 except AttributeError:
2811 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2813 return r[:-1]
2814 return r
2815 return runner
2816
2817
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002818class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002819
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002820 def __str__(self):
2821 return 'prior sync failed; rebase still in progress'
2822
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002823
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002824class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002825
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002826 def __str__(self):
2827 return 'contains uncommitted changes'
2828
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002829
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002830class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002831
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002832 def __init__(self, project, text):
2833 self.project = project
2834 self.text = text
2835
2836 def Print(self, syncbuf):
2837 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2838 syncbuf.out.nl()
2839
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002840
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002841class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002842
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002843 def __init__(self, project, why):
2844 self.project = project
2845 self.why = why
2846
2847 def Print(self, syncbuf):
2848 syncbuf.out.fail('error: %s/: %s',
2849 self.project.relpath,
2850 str(self.why))
2851 syncbuf.out.nl()
2852
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002853
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002854class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002855
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002856 def __init__(self, project, action):
2857 self.project = project
2858 self.action = action
2859
2860 def Run(self, syncbuf):
2861 out = syncbuf.out
2862 out.project('project %s/', self.project.relpath)
2863 out.nl()
2864 try:
2865 self.action()
2866 out.nl()
2867 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002868 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002869 out.nl()
2870 return False
2871
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002872
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002873class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002874
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002875 def __init__(self, config):
2876 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002877 self.project = self.printer('header', attr='bold')
2878 self.info = self.printer('info')
2879 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002880
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002881
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002882class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002883
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002884 def __init__(self, config, detach_head=False):
2885 self._messages = []
2886 self._failures = []
2887 self._later_queue1 = []
2888 self._later_queue2 = []
2889
2890 self.out = _SyncColoring(config)
2891 self.out.redirect(sys.stderr)
2892
2893 self.detach_head = detach_head
2894 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07002895 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002896
2897 def info(self, project, fmt, *args):
2898 self._messages.append(_InfoMessage(project, fmt % args))
2899
2900 def fail(self, project, err=None):
2901 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07002902 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002903
2904 def later1(self, project, what):
2905 self._later_queue1.append(_Later(project, what))
2906
2907 def later2(self, project, what):
2908 self._later_queue2.append(_Later(project, what))
2909
2910 def Finish(self):
2911 self._PrintMessages()
2912 self._RunLater()
2913 self._PrintMessages()
2914 return self.clean
2915
David Rileye0684ad2017-04-05 00:02:59 -07002916 def Recently(self):
2917 recent_clean = self.recent_clean
2918 self.recent_clean = True
2919 return recent_clean
2920
2921 def _MarkUnclean(self):
2922 self.clean = False
2923 self.recent_clean = False
2924
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002925 def _RunLater(self):
2926 for q in ['_later_queue1', '_later_queue2']:
2927 if not self._RunQueue(q):
2928 return
2929
2930 def _RunQueue(self, queue):
2931 for m in getattr(self, queue):
2932 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07002933 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002934 return False
2935 setattr(self, queue, [])
2936 return True
2937
2938 def _PrintMessages(self):
2939 for m in self._messages:
2940 m.Print(self)
2941 for m in self._failures:
2942 m.Print(self)
2943
2944 self._messages = []
2945 self._failures = []
2946
2947
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002949
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002950 """A special project housed under .repo.
2951 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002952
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002953 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002955 manifest=manifest,
2956 name=name,
2957 gitdir=gitdir,
2958 objdir=gitdir,
2959 worktree=worktree,
2960 remote=RemoteSpec('origin'),
2961 relpath='.repo/%s' % name,
2962 revisionExpr='refs/heads/master',
2963 revisionId=None,
2964 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002965
2966 def PreSync(self):
2967 if self.Exists:
2968 cb = self.CurrentBranch
2969 if cb:
2970 base = self.GetBranch(cb).merge
2971 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002972 self.revisionExpr = base
2973 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974
Martin Kelly224a31a2017-07-10 14:46:25 -07002975 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02002976 """ Prepare MetaProject for manifest branch switch
2977 """
2978
2979 # detach and delete manifest branch, allowing a new
2980 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002981 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07002982 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02002983 syncbuf.Finish()
2984
2985 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002986 ['update-ref', '-d', 'refs/heads/default'],
2987 capture_stdout=True,
2988 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002989
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002990 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002991 def LastFetch(self):
2992 try:
2993 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2994 return os.path.getmtime(fh)
2995 except OSError:
2996 return 0
2997
2998 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002999 def HasChanges(self):
3000 """Has the remote received new commits not yet checked out?
3001 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003002 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003003 return False
3004
David Pursehouse8a68ff92012-09-24 12:15:13 +09003005 all_refs = self.bare_ref.all
3006 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003007 head = self.work_git.GetHead()
3008 if head.startswith(R_HEADS):
3009 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003010 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003011 except KeyError:
3012 head = None
3013
3014 if revid == head:
3015 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003016 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003017 return True
3018 return False