blob: d01b8a6339658f8eabdb9e159dc5c404c88f0fb1 [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,
178 draft=False,
Changcheng Xiaodcd9ec92017-08-02 16:55:03 +0200179 private=False,
180 wip=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700181 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800182 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700183 people,
Brian Harring435370c2012-07-28 15:37:04 -0700184 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400185 draft=draft,
Changcheng Xiaodcd9ec92017-08-02 16:55:03 +0200186 private=private,
187 wip=wip,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400188 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700190 def GetPublishedRefs(self):
191 refs = {}
192 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700193 self.branch.remote.SshReviewUrl(self.project.UserEmail),
194 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700195 for line in output.split('\n'):
196 try:
197 (sha, ref) = line.split()
198 refs[sha] = ref
199 except ValueError:
200 pass
201
202 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700204
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700206
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 def __init__(self, config):
208 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100209 self.project = self.printer('header', attr='bold')
210 self.branch = self.printer('header', attr='bold')
211 self.nobranch = self.printer('nobranch', fg='red')
212 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
Anthony King7bdac712014-07-16 12:56:40 +0100214 self.added = self.printer('added', fg='green')
215 self.changed = self.printer('changed', fg='red')
216 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218
219class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 def __init__(self, config):
222 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100223 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700225
Anthony King7bdac712014-07-16 12:56:40 +0100226class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227
James W. Mills24c13082012-04-12 15:04:13 -0500228 def __init__(self, name, value, keep):
229 self.name = name
230 self.value = value
231 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
Anthony King7bdac712014-07-16 12:56:40 +0100234class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800236 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 self.src = src
238 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 self.abs_src = abssrc
240 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
242 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800243 src = self.abs_src
244 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 # copy file if it does not exist or is out of date
246 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
247 try:
248 # remove existing file first, since it might be read-only
249 if os.path.exists(dest):
250 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400251 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200252 dest_dir = os.path.dirname(dest)
253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 shutil.copy(src, dest)
256 # make the file read-only
257 mode = os.stat(dest)[stat.ST_MODE]
258 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
259 os.chmod(dest, mode)
260 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700261 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700263
Anthony King7bdac712014-07-16 12:56:40 +0100264class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700265
Wink Saville4c426ef2015-06-03 08:05:17 -0700266 def __init__(self, git_worktree, src, dest, relsrc, absdest):
267 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500268 self.src = src
269 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700270 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 self.abs_dest = absdest
272
Wink Saville4c426ef2015-06-03 08:05:17 -0700273 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500274 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700275 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 try:
277 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800278 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700279 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500280 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700281 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500282 if not os.path.isdir(dest_dir):
283 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700284 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500285 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700286 _error('Cannot link file %s to %s', relSrc, absDest)
287
288 def _Link(self):
289 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
290 on the src linking all of the files in the source in to the destination
291 directory.
292 """
293 # We use the absSrc to handle the situation where the current directory
294 # is not the root of the repo
295 absSrc = os.path.join(self.git_worktree, self.src)
296 if os.path.exists(absSrc):
297 # Entity exists so just a simple one to one link operation
298 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
299 else:
300 # Entity doesn't exist assume there is a wild card
301 absDestDir = self.abs_dest
302 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
303 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700304 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700305 else:
306 absSrcFiles = glob.glob(absSrc)
307 for absSrcFile in absSrcFiles:
308 # Create a releative path from source dir to destination dir
309 absSrcDir = os.path.dirname(absSrcFile)
310 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
311
312 # Get the source file name
313 srcFile = os.path.basename(absSrcFile)
314
315 # Now form the final full paths to srcFile. They will be
316 # absolute for the desintaiton and relative for the srouce.
317 absDest = os.path.join(absDestDir, srcFile)
318 relSrc = os.path.join(relSrcDir, srcFile)
319 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500320
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700321
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700322class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700323
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700324 def __init__(self,
325 name,
Anthony King7bdac712014-07-16 12:56:40 +0100326 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700327 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100328 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700329 revision=None,
330 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700331 self.name = name
332 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700333 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700334 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100335 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700336 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338
Doug Anderson37282b42011-03-04 11:54:18 -0800339class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700340
Doug Anderson37282b42011-03-04 11:54:18 -0800341 """A RepoHook contains information about a script to run as a hook.
342
343 Hooks are used to run a python script before running an upload (for instance,
344 to run presubmit checks). Eventually, we may have hooks for other actions.
345
346 This shouldn't be confused with files in the 'repo/hooks' directory. Those
347 files are copied into each '.git/hooks' folder for each project. Repo-level
348 hooks are associated instead with repo actions.
349
350 Hooks are always python. When a hook is run, we will load the hook into the
351 interpreter and execute its main() function.
352 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700353
Doug Anderson37282b42011-03-04 11:54:18 -0800354 def __init__(self,
355 hook_type,
356 hooks_project,
357 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400358 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800359 abort_if_user_denies=False):
360 """RepoHook constructor.
361
362 Params:
363 hook_type: A string representing the type of hook. This is also used
364 to figure out the name of the file containing the hook. For
365 example: 'pre-upload'.
366 hooks_project: The project containing the repo hooks. If you have a
367 manifest, this is manifest.repo_hooks_project. OK if this is None,
368 which will make the hook a no-op.
369 topdir: Repo's top directory (the one containing the .repo directory).
370 Scripts will run with CWD as this directory. If you have a manifest,
371 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400372 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800373 abort_if_user_denies: If True, we'll throw a HookError() if the user
374 doesn't allow us to run the hook.
375 """
376 self._hook_type = hook_type
377 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400378 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800379 self._topdir = topdir
380 self._abort_if_user_denies = abort_if_user_denies
381
382 # Store the full path to the script for convenience.
383 if self._hooks_project:
384 self._script_fullpath = os.path.join(self._hooks_project.worktree,
385 self._hook_type + '.py')
386 else:
387 self._script_fullpath = None
388
389 def _GetHash(self):
390 """Return a hash of the contents of the hooks directory.
391
392 We'll just use git to do this. This hash has the property that if anything
393 changes in the directory we will return a different has.
394
395 SECURITY CONSIDERATION:
396 This hash only represents the contents of files in the hook directory, not
397 any other files imported or called by hooks. Changes to imported files
398 can change the script behavior without affecting the hash.
399
400 Returns:
401 A string representing the hash. This will always be ASCII so that it can
402 be printed to the user easily.
403 """
404 assert self._hooks_project, "Must have hooks to calculate their hash."
405
406 # We will use the work_git object rather than just calling GetRevisionId().
407 # That gives us a hash of the latest checked in version of the files that
408 # the user will actually be executing. Specifically, GetRevisionId()
409 # doesn't appear to change even if a user checks out a different version
410 # of the hooks repo (via git checkout) nor if a user commits their own revs.
411 #
412 # NOTE: Local (non-committed) changes will not be factored into this hash.
413 # I think this is OK, since we're really only worried about warning the user
414 # about upstream changes.
415 return self._hooks_project.work_git.rev_parse('HEAD')
416
417 def _GetMustVerb(self):
418 """Return 'must' if the hook is required; 'should' if not."""
419 if self._abort_if_user_denies:
420 return 'must'
421 else:
422 return 'should'
423
424 def _CheckForHookApproval(self):
425 """Check to see whether this hook has been approved.
426
Mike Frysinger40252c22016-08-15 21:23:44 -0400427 We'll accept approval of manifest URLs if they're using secure transports.
428 This way the user can say they trust the manifest hoster. For insecure
429 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800430
431 Note that we ask permission for each individual hook even though we use
432 the hash of all hooks when detecting changes. We'd like the user to be
433 able to approve / deny each hook individually. We only use the hash of all
434 hooks because there is no other easy way to detect changes to local imports.
435
436 Returns:
437 True if this hook is approved to run; False otherwise.
438
439 Raises:
440 HookError: Raised if the user doesn't approve and abort_if_user_denies
441 was passed to the consturctor.
442 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400443 if self._ManifestUrlHasSecureScheme():
444 return self._CheckForHookApprovalManifest()
445 else:
446 return self._CheckForHookApprovalHash()
447
448 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
449 changed_prompt):
450 """Check for approval for a particular attribute and hook.
Doug Anderson37282b42011-03-04 11:54:18 -0800451
Mike Frysinger40252c22016-08-15 21:23:44 -0400452 Args:
453 subkey: The git config key under [repo.hooks.<hook_type>] to store the
454 last approved string.
455 new_val: The new value to compare against the last approved one.
456 main_prompt: Message to display to the user to ask for approval.
457 changed_prompt: Message explaining why we're re-asking for approval.
Doug Anderson37282b42011-03-04 11:54:18 -0800458
Mike Frysinger40252c22016-08-15 21:23:44 -0400459 Returns:
460 True if this hook is approved to run; False otherwise.
Doug Anderson37282b42011-03-04 11:54:18 -0800461
Mike Frysinger40252c22016-08-15 21:23:44 -0400462 Raises:
463 HookError: Raised if the user doesn't approve and abort_if_user_denies
464 was passed to the consturctor.
465 """
466 hooks_config = self._hooks_project.config
467 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800468
Mike Frysinger40252c22016-08-15 21:23:44 -0400469 # Get the last value that the user approved for this hook; may be None.
470 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800471
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800473 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800475 # Approval matched. We're done.
476 return True
477 else:
478 # Give the user a reason why we're prompting, since they last told
479 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400480 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800481 else:
482 prompt = ''
483
484 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
485 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400486 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530487 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900488 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800489
490 # User is doing a one-time approval.
491 if response in ('y', 'yes'):
492 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400493 elif response == 'always':
494 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800495 return True
496
497 # For anything else, we'll assume no approval.
498 if self._abort_if_user_denies:
499 raise HookError('You must allow the %s hook or use --no-verify.' %
500 self._hook_type)
501
502 return False
503
Mike Frysinger40252c22016-08-15 21:23:44 -0400504 def _ManifestUrlHasSecureScheme(self):
505 """Check if the URI for the manifest is a secure transport."""
506 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
507 parse_results = urllib.parse.urlparse(self._manifest_url)
508 return parse_results.scheme in secure_schemes
509
510 def _CheckForHookApprovalManifest(self):
511 """Check whether the user has approved this manifest host.
512
513 Returns:
514 True if this hook is approved to run; False otherwise.
515 """
516 return self._CheckForHookApprovalHelper(
517 'approvedmanifest',
518 self._manifest_url,
519 'Run hook scripts from %s' % (self._manifest_url,),
520 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
521
522 def _CheckForHookApprovalHash(self):
523 """Check whether the user has approved the hooks repo.
524
525 Returns:
526 True if this hook is approved to run; False otherwise.
527 """
528 prompt = ('Repo %s run the script:\n'
529 ' %s\n'
530 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700531 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400532 return self._CheckForHookApprovalHelper(
533 'approvedhash',
534 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700535 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400536 'Scripts have changed since %s was allowed.' % (self._hook_type,))
537
Doug Anderson37282b42011-03-04 11:54:18 -0800538 def _ExecuteHook(self, **kwargs):
539 """Actually execute the given hook.
540
541 This will run the hook's 'main' function in our python interpreter.
542
543 Args:
544 kwargs: Keyword arguments to pass to the hook. These are often specific
545 to the hook type. For instance, pre-upload hooks will contain
546 a project_list.
547 """
548 # Keep sys.path and CWD stashed away so that we can always restore them
549 # upon function exit.
550 orig_path = os.getcwd()
551 orig_syspath = sys.path
552
553 try:
554 # Always run hooks with CWD as topdir.
555 os.chdir(self._topdir)
556
557 # Put the hook dir as the first item of sys.path so hooks can do
558 # relative imports. We want to replace the repo dir as [0] so
559 # hooks can't import repo files.
560 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
561
562 # Exec, storing global context in the context dict. We catch exceptions
563 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500564 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800565 try:
Anthony King70f68902014-05-05 21:15:34 +0100566 exec(compile(open(self._script_fullpath).read(),
567 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800568 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700569 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
570 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800571
572 # Running the script should have defined a main() function.
573 if 'main' not in context:
574 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
575
Doug Anderson37282b42011-03-04 11:54:18 -0800576 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
577 # We don't actually want hooks to define their main with this argument--
578 # it's there to remind them that their hook should always take **kwargs.
579 # For instance, a pre-upload hook should be defined like:
580 # def main(project_list, **kwargs):
581 #
582 # This allows us to later expand the API without breaking old hooks.
583 kwargs = kwargs.copy()
584 kwargs['hook_should_take_kwargs'] = True
585
586 # Call the main function in the hook. If the hook should cause the
587 # build to fail, it will raise an Exception. We'll catch that convert
588 # to a HookError w/ just the failing traceback.
589 try:
590 context['main'](**kwargs)
591 except Exception:
592 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700593 'above.' % (traceback.format_exc(),
594 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800595 finally:
596 # Restore sys.path and CWD.
597 sys.path = orig_syspath
598 os.chdir(orig_path)
599
600 def Run(self, user_allows_all_hooks, **kwargs):
601 """Run the hook.
602
603 If the hook doesn't exist (because there is no hooks project or because
604 this particular hook is not enabled), this is a no-op.
605
606 Args:
607 user_allows_all_hooks: If True, we will never prompt about running the
608 hook--we'll just assume it's OK to run it.
609 kwargs: Keyword arguments to pass to the hook. These are often specific
610 to the hook type. For instance, pre-upload hooks will contain
611 a project_list.
612
613 Raises:
614 HookError: If there was a problem finding the hook or the user declined
615 to run a required hook (from _CheckForHookApproval).
616 """
617 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700618 if ((not self._hooks_project) or (self._hook_type not in
619 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800620 return
621
622 # Bail with a nice error if we can't find the hook.
623 if not os.path.isfile(self._script_fullpath):
624 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
625
626 # Make sure the user is OK with running the hook.
627 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
628 return
629
630 # Run the hook with the same version of python we're using.
631 self._ExecuteHook(**kwargs)
632
633
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600635 # These objects can be shared between several working trees.
636 shareable_files = ['description', 'info']
natalie.chene8996f92015-12-29 10:53:30 +0800637 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn', 'lfs']
Kevin Degi384b3c52014-10-16 16:02:58 -0600638 # These objects can only be used by a single working tree.
639 working_tree_files = ['config', 'packed-refs', 'shallow']
640 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 def __init__(self,
643 manifest,
644 name,
645 remote,
646 gitdir,
David James8d201162013-10-11 17:03:19 -0700647 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700648 worktree,
649 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700650 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800651 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100652 rebase=True,
653 groups=None,
654 sync_c=False,
655 sync_s=False,
656 clone_depth=None,
657 upstream=None,
658 parent=None,
659 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900660 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700661 optimized_fetch=False,
natalie.chene8996f92015-12-29 10:53:30 +0800662 old_revision=None,
663 lfs_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800664 """Init a Project object.
665
666 Args:
667 manifest: The XmlManifest object.
668 name: The `name` attribute of manifest.xml's project element.
669 remote: RemoteSpec object specifying its remote's properties.
670 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700671 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800672 worktree: Absolute path of git working tree.
673 relpath: Relative path of git working tree to repo's top directory.
674 revisionExpr: The `revision` attribute of manifest.xml's project element.
675 revisionId: git commit id for checking out.
676 rebase: The `rebase` attribute of manifest.xml's project element.
677 groups: The `groups` attribute of manifest.xml's project element.
678 sync_c: The `sync-c` attribute of manifest.xml's project element.
679 sync_s: The `sync-s` attribute of manifest.xml's project element.
680 upstream: The `upstream` attribute of manifest.xml's project element.
681 parent: The parent Project object.
682 is_derived: False if the project was explicitly defined in the manifest;
683 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400684 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900685 optimized_fetch: If True, when a project is set to a sha1 revision, only
686 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700687 old_revision: saved git commit id for open GITC projects.
natalie.chene8996f92015-12-29 10:53:30 +0800688 lfs_fetch: git lfs fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800689 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 self.manifest = manifest
691 self.name = name
692 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800693 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700694 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800695 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700696 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800697 else:
698 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700700 self.revisionExpr = revisionExpr
701
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700702 if revisionId is None \
703 and revisionExpr \
704 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700705 self.revisionId = revisionExpr
706 else:
707 self.revisionId = revisionId
708
Mike Pontillod3153822012-02-28 11:53:24 -0800709 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700710 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700711 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800712 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900713 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700714 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800715 self.parent = parent
716 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900717 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800718 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500722 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500723 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700724 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
725 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800727 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700728 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800729 else:
730 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700731 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700732 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700733 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400734 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700735 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
Doug Anderson37282b42011-03-04 11:54:18 -0800737 # This will be filled in if a project is later identified to be the
738 # project containing repo hooks.
739 self.enabled_repo_hooks = []
natalie.chene8996f92015-12-29 10:53:30 +0800740 self.lfs_fetch = lfs_fetch
Doug Anderson37282b42011-03-04 11:54:18 -0800741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800743 def Derived(self):
744 return self.is_derived
745
746 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600748 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749
750 @property
751 def CurrentBranch(self):
752 """Obtain the name of the currently checked out branch.
753 The branch name omits the 'refs/heads/' prefix.
754 None is returned if the project is on a detached HEAD.
755 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700756 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 if b.startswith(R_HEADS):
758 return b[len(R_HEADS):]
759 return None
760
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700761 def IsRebaseInProgress(self):
762 w = self.worktree
763 g = os.path.join(w, '.git')
764 return os.path.exists(os.path.join(g, 'rebase-apply')) \
765 or os.path.exists(os.path.join(g, 'rebase-merge')) \
766 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 def IsDirty(self, consider_untracked=True):
769 """Is the working directory modified in some way?
770 """
771 self.work_git.update_index('-q',
772 '--unmerged',
773 '--ignore-missing',
774 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900775 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 return True
777 if self.work_git.DiffZ('diff-files'):
778 return True
779 if consider_untracked and self.work_git.LsOthers():
780 return True
781 return False
782
783 _userident_name = None
784 _userident_email = None
785
786 @property
787 def UserName(self):
788 """Obtain the user's personal name.
789 """
790 if self._userident_name is None:
791 self._LoadUserIdentity()
792 return self._userident_name
793
794 @property
795 def UserEmail(self):
796 """Obtain the user's email address. This is very likely
797 to be their Gerrit login.
798 """
799 if self._userident_email is None:
800 self._LoadUserIdentity()
801 return self._userident_email
802
803 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900804 u = self.bare_git.var('GIT_COMMITTER_IDENT')
805 m = re.compile("^(.*) <([^>]*)> ").match(u)
806 if m:
807 self._userident_name = m.group(1)
808 self._userident_email = m.group(2)
809 else:
810 self._userident_name = ''
811 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
813 def GetRemote(self, name):
814 """Get the configuration for a single remote.
815 """
816 return self.config.GetRemote(name)
817
818 def GetBranch(self, name):
819 """Get the configuration for a single branch.
820 """
821 return self.config.GetBranch(name)
822
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700823 def GetBranches(self):
824 """Get all existing local branches.
825 """
826 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700828 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700829
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530830 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831 if name.startswith(R_HEADS):
832 name = name[len(R_HEADS):]
833 b = self.GetBranch(name)
834 b.current = name == current
835 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900836 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700837 heads[name] = b
838
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530839 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700840 if name.startswith(R_PUB):
841 name = name[len(R_PUB):]
842 b = heads.get(name)
843 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700845
846 return heads
847
Colin Cross5acde752012-03-28 20:15:45 -0700848 def MatchesGroups(self, manifest_groups):
849 """Returns true if the manifest groups specified at init should cause
850 this project to be synced.
851 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700852 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700853
Conley Owens971de8e2012-04-16 10:36:08 -0700854 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700855 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700856 manifest_groups: "-group1,group2"
857 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500858
859 The special manifest group "default" will match any project that
860 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700861 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500862 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700863 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700864 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500865 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700866
Conley Owens971de8e2012-04-16 10:36:08 -0700867 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700868 for group in expanded_manifest_groups:
869 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700870 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700871 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700872 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700873
Conley Owens971de8e2012-04-16 10:36:08 -0700874 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700876# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700877 def UncommitedFiles(self, get_all=True):
878 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700880 Args:
881 get_all: a boolean, if True - get information about all different
882 uncommitted files. If False - return as soon as any kind of
883 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500884 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700885 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500886 self.work_git.update_index('-q',
887 '--unmerged',
888 '--ignore-missing',
889 '--refresh')
890 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700891 details.append("rebase in progress")
892 if not get_all:
893 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500894
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700895 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
896 if changes:
897 details.extend(changes)
898 if not get_all:
899 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500900
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700901 changes = self.work_git.DiffZ('diff-files').keys()
902 if changes:
903 details.extend(changes)
904 if not get_all:
905 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500906
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700907 changes = self.work_git.LsOthers()
908 if changes:
909 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500910
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700911 return details
912
913 def HasChanges(self):
914 """Returns true if there are uncommitted changes.
915 """
916 if self.UncommitedFiles(get_all=False):
917 return True
918 else:
919 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500920
Terence Haddock4655e812011-03-31 12:33:34 +0200921 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200923
924 Args:
925 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 """
927 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700928 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200929 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700930 print(file=output_redir)
931 print('project %s/' % self.relpath, file=output_redir)
932 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 return
934
935 self.work_git.update_index('-q',
936 '--unmerged',
937 '--ignore-missing',
938 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700939 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
941 df = self.work_git.DiffZ('diff-files')
942 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100943 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700944 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945
946 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700947 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200948 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700949 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950
951 branch = self.CurrentBranch
952 if branch is None:
953 out.nobranch('(*** NO BRANCH ***)')
954 else:
955 out.branch('branch %s', branch)
956 out.nl()
957
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700958 if rb:
959 out.important('prior sync failed; rebase still in progress')
960 out.nl()
961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 paths = list()
963 paths.extend(di.keys())
964 paths.extend(df.keys())
965 paths.extend(do)
966
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530967 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900968 try:
969 i = di[p]
970 except KeyError:
971 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900973 try:
974 f = df[p]
975 except KeyError:
976 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200977
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900978 if i:
979 i_status = i.status.upper()
980 else:
981 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900983 if f:
984 f_status = f.status.lower()
985 else:
986 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
988 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800989 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700990 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 else:
992 line = ' %s%s\t%s' % (i_status, f_status, p)
993
994 if i and not f:
995 out.added('%s', line)
996 elif (i and f) or (not i and f):
997 out.changed('%s', line)
998 elif not i and not f:
999 out.untracked('%s', line)
1000 else:
1001 out.write('%s', line)
1002 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001003
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001004 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005
pelyad67872d2012-03-28 14:49:58 +03001006 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 """Prints the status of the repository to stdout.
1008 """
1009 out = DiffColoring(self.config)
1010 cmd = ['diff']
1011 if out.is_on:
1012 cmd.append('--color')
1013 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001014 if absolute_paths:
1015 cmd.append('--src-prefix=a/%s/' % self.relpath)
1016 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 cmd.append('--')
1018 p = GitCommand(self,
1019 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001020 capture_stdout=True,
1021 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 has_diff = False
1023 for line in p.process.stdout:
1024 if not has_diff:
1025 out.nl()
1026 out.project('project %s/' % self.relpath)
1027 out.nl()
1028 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001029 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030 p.Wait()
1031
1032
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001033# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
David Pursehouse8a68ff92012-09-24 12:15:13 +09001035 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 """Was the branch published (uploaded) for code review?
1037 If so, returns the SHA-1 hash of the last published
1038 state for the branch.
1039 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001040 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001041 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001042 try:
1043 return self.bare_git.rev_parse(key)
1044 except GitError:
1045 return None
1046 else:
1047 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001049 except KeyError:
1050 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051
David Pursehouse8a68ff92012-09-24 12:15:13 +09001052 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 """Prunes any stale published refs.
1054 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001055 if all_refs is None:
1056 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057 heads = set()
1058 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301059 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 if name.startswith(R_HEADS):
1061 heads.add(name)
1062 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301065 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 n = name[len(R_PUB):]
1067 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001068 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001070 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 """List any branches which can be uploaded for review.
1072 """
1073 heads = {}
1074 pubed = {}
1075
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301076 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001078 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001080 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301083 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001084 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001086 if selected_branch and branch != selected_branch:
1087 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001089 rb = self.GetUploadableBranch(branch)
1090 if rb:
1091 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092 return ready
1093
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001094 def GetUploadableBranch(self, branch_name):
1095 """Get a single uploadable branch, or None.
1096 """
1097 branch = self.GetBranch(branch_name)
1098 base = branch.LocalMerge
1099 if branch.LocalMerge:
1100 rb = ReviewableBranch(self, branch, base)
1101 if rb.commits:
1102 return rb
1103 return None
1104
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001105 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001106 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001107 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001108 draft=False,
Changcheng Xiaodcd9ec92017-08-02 16:55:03 +02001109 private=False,
1110 wip=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001111 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 """Uploads the named branch for code review.
1113 """
1114 if branch is None:
1115 branch = self.CurrentBranch
1116 if branch is None:
1117 raise GitError('not currently on a branch')
1118
1119 branch = self.GetBranch(branch)
1120 if not branch.LocalMerge:
1121 raise GitError('branch %s does not track a remote' % branch.name)
1122 if not branch.remote.review:
1123 raise GitError('remote %s has no review url' % branch.remote.name)
1124
Bryan Jacobsf609f912013-05-06 13:36:24 -04001125 if dest_branch is None:
1126 dest_branch = self.dest_branch
1127 if dest_branch is None:
1128 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 if not dest_branch.startswith(R_HEADS):
1130 dest_branch = R_HEADS + dest_branch
1131
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001132 if not branch.remote.projectname:
1133 branch.remote.projectname = self.name
1134 branch.remote.Save()
1135
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001136 url = branch.remote.ReviewUrl(self.UserEmail)
1137 if url is None:
1138 raise UploadError('review not configured')
1139 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001140
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001141 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001142 rp = ['gerrit receive-pack']
1143 for e in people[0]:
1144 rp.append('--reviewer=%s' % sq(e))
1145 for e in people[1]:
1146 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001147 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001148
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001149 cmd.append(url)
1150
1151 if dest_branch.startswith(R_HEADS):
1152 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001153
1154 upload_type = 'for'
1155 if draft:
1156 upload_type = 'drafts'
1157
1158 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1159 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001160 if auto_topic:
1161 ref_spec = ref_spec + '/' + branch.name
Changcheng Xiaodcd9ec92017-08-02 16:55:03 +02001162
Shawn Pearce45d21682013-02-28 00:35:51 -08001163 if not url.startswith('ssh://'):
1164 rp = ['r=%s' % p for p in people[0]] + \
1165 ['cc=%s' % p for p in people[1]]
Changcheng Xiaodcd9ec92017-08-02 16:55:03 +02001166 if private:
1167 rp = rp + ['private']
1168 if wip:
1169 rp = rp + ['wip']
Shawn Pearce45d21682013-02-28 00:35:51 -08001170 if rp:
1171 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001172 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001173
Anthony King7bdac712014-07-16 12:56:40 +01001174 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001175 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
1177 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1178 self.bare_git.UpdateRef(R_PUB + branch.name,
1179 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001180 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
1182
natalie.chene8996f92015-12-29 10:53:30 +08001183 def __FetchLfsObjects(self, name, refs):
1184 if 'refs/heads/*' in refs:
1185 refs = []
1186 for ref in GitRefs(self.objdir).all:
1187 if ref.startswith(R_HEADS):
1188 refs.append(ref)
1189 cmd = ['lfs', 'fetch']
1190 cmd.append(name)
1191 cmd.extend(refs)
1192 gitcmd = GitCommand(self, cmd, bare=True)
1193 ret = gitcmd.Wait()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194
natalie.chen9a61a912017-05-26 16:35:48 +08001195 def _CreateLocalMergeBranch(self, branch, revid):
1196 if not branch and not ID_RE.match(revid):
1197 return
1198 ref = os.path.join(self.gitdir, branch)
1199 if os.path.exists(ref): return
natalie.chenf453ba82017-04-21 16:46:39 +08001200 try:
natalie.chen9a61a912017-05-26 16:35:48 +08001201 self.work_git.rev_parse(branch)
natalie.chenf453ba82017-04-21 16:46:39 +08001202 except GitError:
natalie.chenf453ba82017-04-21 16:46:39 +08001203 try:
1204 os.makedirs(os.path.dirname(ref))
1205 except OSError:
1206 pass
1207 _lwrite(ref, '%s\n' % revid)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001208# Sync ##
natalie.chenf453ba82017-04-21 16:46:39 +08001209
Julien Campergue335f5ef2013-10-16 11:02:35 +02001210 def _ExtractArchive(self, tarpath, path=None):
1211 """Extract the given tar on its current location
1212
1213 Args:
1214 - tarpath: The path to the actual tar file
1215
1216 """
1217 try:
1218 with tarfile.open(tarpath, 'r') as tar:
1219 tar.extractall(path=path)
1220 return True
1221 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001222 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001223 return False
1224
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001225 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001226 quiet=False,
1227 is_new=None,
1228 current_branch_only=False,
1229 force_sync=False,
1230 clone_bundle=True,
1231 no_tags=False,
1232 archive=False,
1233 optimized_fetch=False,
1234 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 """Perform only the network IO portion of the sync process.
1236 Local working directory/branch state is not affected.
1237 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001238 if archive and not isinstance(self, MetaProject):
1239 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001240 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001241 return False
1242
1243 name = self.relpath.replace('\\', '/')
1244 name = name.replace('/', '_')
1245 tarpath = '%s.tar' % name
1246 topdir = self.manifest.topdir
1247
1248 try:
1249 self._FetchArchive(tarpath, cwd=topdir)
1250 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001251 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001252 return False
1253
1254 # From now on, we only need absolute tarpath
1255 tarpath = os.path.join(topdir, tarpath)
1256
1257 if not self._ExtractArchive(tarpath, path=topdir):
1258 return False
1259 try:
1260 os.remove(tarpath)
1261 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001262 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001263 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001264 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001265 if is_new is None:
1266 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001267 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001268 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001269 else:
1270 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001272
1273 if is_new:
1274 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1275 try:
1276 fd = open(alt, 'rb')
1277 try:
1278 alt_dir = fd.readline().rstrip()
1279 finally:
1280 fd.close()
1281 except IOError:
1282 alt_dir = None
1283 else:
1284 alt_dir = None
1285
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001286 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001287 and alt_dir is None \
1288 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001289 is_new = False
1290
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001291 if not current_branch_only:
1292 if self.sync_c:
1293 current_branch_only = True
1294 elif not self.manifest._loaded:
1295 # Manifest cannot check defaults until it syncs.
1296 current_branch_only = False
1297 elif self.manifest.default.sync_c:
1298 current_branch_only = True
1299
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001300 need_to_fetch = not (optimized_fetch and
1301 (ID_RE.match(self.revisionExpr) and
1302 self._CheckForSha1()))
1303 if (need_to_fetch and
1304 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1305 current_branch_only=current_branch_only,
1306 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001307 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001308
1309 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001310 self._InitMRef()
1311 else:
1312 self._InitMirrorHead()
1313 try:
1314 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1315 except OSError:
1316 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001318
1319 def PostRepoUpgrade(self):
1320 self._InitHooks()
1321
natalie.chendc3502d2017-05-19 17:01:37 +08001322 def RemoveOldCopyAndLinkFiles(self, path = None):
1323 old_copylink = []
1324 file_path = os.path.join(self.gitdir if not path else path, '.repo_copylink')
1325 if os.path.exists(file_path):
1326 fd = open(file_path, 'r')
1327 try:
1328 old_copylink = fd.read().split('\n')
1329 finally:
1330 fd.close()
natalie.chen1db7cb62017-08-16 09:23:19 +08001331 for dest in old_copylink:
1332 if not dest: continue
1333 target = os.path.join(self.manifest.topdir, dest)
natalie.chen19593222018-02-05 20:49:35 +08001334 if os.path.lexists(target):
natalie.chendc3502d2017-05-19 17:01:37 +08001335 if IsTrace():
1336 Trace('rm %s', target)
1337 os.remove(target)
1338
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001339 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001340 if self.manifest.isGitcClient:
1341 return
natalie.chendc3502d2017-05-19 17:01:37 +08001342 self.RemoveOldCopyAndLinkFiles()
1343 new_copylink = []
David Pursehouse8a68ff92012-09-24 12:15:13 +09001344 for copyfile in self.copyfiles:
1345 copyfile._Copy()
natalie.chen1db7cb62017-08-16 09:23:19 +08001346 new_copylink.append(copyfile.dest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001347 for linkfile in self.linkfiles:
1348 linkfile._Link()
natalie.chen1db7cb62017-08-16 09:23:19 +08001349 new_copylink.append(linkfile.dest)
natalie.chendc3502d2017-05-19 17:01:37 +08001350 file_path = os.path.join(self.gitdir, '.repo_copylink')
1351 fd = open(file_path, 'w')
1352 try:
1353 fd.write('\n'.join(new_copylink))
1354 fd.write('\n')
1355 finally:
1356 fd.close()
1357
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Julien Camperguedd654222014-01-09 16:21:37 +01001359 def GetCommitRevisionId(self):
1360 """Get revisionId of a commit.
1361
1362 Use this method instead of GetRevisionId to get the id of the commit rather
1363 than the id of the current git object (for example, a tag)
1364
1365 """
1366 if not self.revisionExpr.startswith(R_TAGS):
1367 return self.GetRevisionId(self._allrefs)
1368
1369 try:
1370 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1371 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001372 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1373 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001374
David Pursehouse8a68ff92012-09-24 12:15:13 +09001375 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001376 if self.revisionId:
1377 return self.revisionId
1378
1379 rem = self.GetRemote(self.remote.name)
1380 rev = rem.ToLocal(self.revisionExpr)
1381
David Pursehouse8a68ff92012-09-24 12:15:13 +09001382 if all_refs is not None and rev in all_refs:
1383 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001384
1385 try:
1386 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1387 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001388 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1389 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390
Kevin Degiabaa7f32014-11-12 11:27:45 -07001391 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392 """Perform only the local IO portion of the sync process.
1393 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001395 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001396 all_refs = self.bare_ref.all
1397 self.CleanPublishedCache(all_refs)
1398 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001399
David Pursehouse1d947b32012-10-25 12:23:11 +09001400 def _doff():
1401 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001402 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001403
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001404 head = self.work_git.GetHead()
1405 if head.startswith(R_HEADS):
1406 branch = head[len(R_HEADS):]
1407 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001408 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001409 except KeyError:
1410 head = None
1411 else:
1412 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001414 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415 # Currently on a detached HEAD. The user is assumed to
1416 # not have any local modifications worth worrying about.
1417 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001418 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001419 syncbuf.fail(self, _PriorSyncFailedError())
1420 return
1421
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001422 if head == revid:
1423 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001424 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001425 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001426 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001427 # The copy/linkfile config may have changed.
1428 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001429 return
1430 else:
1431 lost = self._revlist(not_rev(revid), HEAD)
1432 if lost:
1433 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001434
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001436 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001437 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001438 syncbuf.fail(self, e)
1439 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001440 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001441 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001442
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001443 if head == revid:
1444 # No changes; don't do anything further.
1445 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001446 # The copy/linkfile config may have changed.
1447 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001448 return
1449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001450 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001451
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001452 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001454 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001455 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001456 syncbuf.info(self,
1457 "leaving %s; does not track upstream",
1458 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001460 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001461 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001462 syncbuf.fail(self, e)
1463 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001464 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001465 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001468 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001469 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001470 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471 if not_merged:
1472 if upstream_gain:
1473 # The user has published this branch and some of those
1474 # commits are not yet merged upstream. We do not want
1475 # to rewrite the published commits so we punt.
1476 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001477 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001478 "branch %s is published (but not merged) and is now "
1479 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001480 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001481 elif pub == head:
1482 # All published commits are merged, and thus we are a
1483 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001484 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001485 syncbuf.later1(self, _doff)
1486 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001488 # Examine the local commits not in the remote. Find the
1489 # last one attributed to this user, if any.
1490 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001491 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001492 last_mine = None
1493 cnt_mine = 0
1494 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301495 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001496 if committer_email == self.UserEmail:
1497 last_mine = commit_id
1498 cnt_mine += 1
1499
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001500 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001501 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502
1503 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001504 syncbuf.fail(self, _DirtyError())
1505 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001506
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001507 # If the upstream switched on us, warn the user.
1508 #
1509 if branch.merge != self.revisionExpr:
1510 if branch.merge and self.revisionExpr:
1511 syncbuf.info(self,
1512 'manifest switched %s...%s',
1513 branch.merge,
1514 self.revisionExpr)
1515 elif branch.merge:
1516 syncbuf.info(self,
1517 'manifest no longer tracks %s',
1518 branch.merge)
1519
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001520 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001521 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001522 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001524 syncbuf.info(self,
1525 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001526 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001528 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001529 if not ID_RE.match(self.revisionExpr):
1530 # in case of manifest sync the revisionExpr might be a SHA1
1531 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001532 if not branch.merge.startswith('refs/'):
1533 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001534 branch.Save()
1535
Mike Pontillod3153822012-02-28 11:53:24 -08001536 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001537 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001538 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001539 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001540 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001541 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001542 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001543 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001544 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001545 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001546 syncbuf.fail(self, e)
1547 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001549 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001550
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001551 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552 # dest should already be an absolute path, but src is project relative
1553 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001554 abssrc = os.path.join(self.worktree, src)
1555 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001557 def AddLinkFile(self, src, dest, absdest):
1558 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001559 # make src relative path to dest
1560 absdestdir = os.path.dirname(absdest)
1561 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001562 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001563
James W. Mills24c13082012-04-12 15:04:13 -05001564 def AddAnnotation(self, name, value, keep):
1565 self.annotations.append(_Annotation(name, value, keep))
1566
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001567 def DownloadPatchSet(self, change_id, patch_id):
1568 """Download a single patch set of a single change to FETCH_HEAD.
1569 """
1570 remote = self.GetRemote(self.remote.name)
1571
1572 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001573 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001574 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001575 if GitCommand(self, cmd, bare=True).Wait() != 0:
1576 return None
1577 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001578 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001579 change_id,
1580 patch_id,
1581 self.bare_git.rev_parse('FETCH_HEAD'))
1582
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001583
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001584# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585
Simran Basib9a1b732015-08-20 12:19:28 -07001586 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001587 """Create a new branch off the manifest's revision.
1588 """
Simran Basib9a1b732015-08-20 12:19:28 -07001589 if not branch_merge:
1590 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591 head = self.work_git.GetHead()
1592 if head == (R_HEADS + name):
1593 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594
David Pursehouse8a68ff92012-09-24 12:15:13 +09001595 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001596 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001597 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001598 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001599 capture_stdout=True,
1600 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001601
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001602 branch = self.GetBranch(name)
1603 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001604 branch.merge = branch_merge
1605 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1606 branch.merge = R_HEADS + branch_merge
natalie.chen9a61a912017-05-26 16:35:48 +08001607 '''make sure local merge branch exists'''
1608 self._CreateLocalMergeBranch(branch.LocalMerge, self.revisionExpr)
1609
David Pursehouse8a68ff92012-09-24 12:15:13 +09001610 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001611
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001612 if head.startswith(R_HEADS):
1613 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001614 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001615 except KeyError:
1616 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001617 if revid and head and revid == head:
1618 ref = os.path.join(self.gitdir, R_HEADS + name)
1619 try:
1620 os.makedirs(os.path.dirname(ref))
1621 except OSError:
1622 pass
1623 _lwrite(ref, '%s\n' % revid)
1624 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1625 'ref: %s%s\n' % (R_HEADS, name))
1626 branch.Save()
1627 return True
1628
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001629 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001630 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001631 capture_stdout=True,
1632 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001633 branch.Save()
1634 return True
1635 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001636
Wink Saville02d79452009-04-10 13:01:24 -07001637 def CheckoutBranch(self, name):
1638 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001639
1640 Args:
1641 name: The name of the branch to checkout.
1642
1643 Returns:
1644 True if the checkout succeeded; False if it didn't; None if the branch
1645 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001646 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001647 rev = R_HEADS + name
1648 head = self.work_git.GetHead()
1649 if head == rev:
1650 # Already on the branch
1651 #
1652 return True
Wink Saville02d79452009-04-10 13:01:24 -07001653
David Pursehouse8a68ff92012-09-24 12:15:13 +09001654 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001655 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001656 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001657 except KeyError:
1658 # Branch does not exist in this project
1659 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001660 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001661
1662 if head.startswith(R_HEADS):
1663 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001665 except KeyError:
1666 head = None
1667
1668 if head == revid:
1669 # Same revision; just update HEAD to point to the new
1670 # target branch, but otherwise take no other action.
1671 #
1672 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1673 'ref: %s%s\n' % (R_HEADS, name))
1674 return True
Wink Saville02d79452009-04-10 13:01:24 -07001675
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001676 return GitCommand(self,
1677 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001678 capture_stdout=True,
1679 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001680
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001681 def AbandonBranch(self, name):
1682 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001683
1684 Args:
1685 name: The name of the branch to abandon.
1686
1687 Returns:
1688 True if the abandon succeeded; False if it didn't; None if the branch
1689 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001690 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001691 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001692 all_refs = self.bare_ref.all
1693 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001694 # Doesn't exist
1695 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001696
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001697 head = self.work_git.GetHead()
1698 if head == rev:
1699 # We can't destroy the branch while we are sitting
1700 # on it. Switch to a detached HEAD.
1701 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001702 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001703
David Pursehouse8a68ff92012-09-24 12:15:13 +09001704 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001705 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001706 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1707 '%s\n' % revid)
1708 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001709 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001710
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001711 return GitCommand(self,
1712 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001713 capture_stdout=True,
1714 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001715
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001716 def PruneHeads(self):
1717 """Prune any topic branches already merged into upstream.
1718 """
1719 cb = self.CurrentBranch
1720 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001721 left = self._allrefs
1722 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723 if name.startswith(R_HEADS):
1724 name = name[len(R_HEADS):]
1725 if cb is None or name != cb:
1726 kill.append(name)
1727
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001728 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729 if cb is not None \
1730 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001731 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732 self.work_git.DetachHead(HEAD)
1733 kill.append(cb)
1734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001736 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 try:
1739 self.bare_git.DetachHead(rev)
1740
1741 b = ['branch', '-d']
1742 b.extend(kill)
1743 b = GitCommand(self, b, bare=True,
1744 capture_stdout=True,
1745 capture_stderr=True)
1746 b.Wait()
1747 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001748 if ID_RE.match(old):
1749 self.bare_git.DetachHead(old)
1750 else:
1751 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001752 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001754 for branch in kill:
1755 if (R_HEADS + branch) not in left:
1756 self.CleanPublishedCache()
1757 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758
1759 if cb and cb not in kill:
1760 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001761 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762
1763 kept = []
1764 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001765 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 branch = self.GetBranch(branch)
1767 base = branch.LocalMerge
1768 if not base:
1769 base = rev
1770 kept.append(ReviewableBranch(self, branch, base))
1771 return kept
1772
1773
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001774# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001775
1776 def GetRegisteredSubprojects(self):
1777 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001778
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001779 def rec(subprojects):
1780 if not subprojects:
1781 return
1782 result.extend(subprojects)
1783 for p in subprojects:
1784 rec(p.subprojects)
1785 rec(self.subprojects)
1786 return result
1787
1788 def _GetSubmodules(self):
1789 # Unfortunately we cannot call `git submodule status --recursive` here
1790 # because the working tree might not exist yet, and it cannot be used
1791 # without a working tree in its current implementation.
1792
1793 def get_submodules(gitdir, rev):
1794 # Parse .gitmodules for submodule sub_paths and sub_urls
1795 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1796 if not sub_paths:
1797 return []
1798 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1799 # revision of submodule repository
1800 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1801 submodules = []
1802 for sub_path, sub_url in zip(sub_paths, sub_urls):
1803 try:
1804 sub_rev = sub_revs[sub_path]
1805 except KeyError:
1806 # Ignore non-exist submodules
1807 continue
1808 submodules.append((sub_rev, sub_path, sub_url))
1809 return submodules
1810
1811 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1812 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001813
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001814 def parse_gitmodules(gitdir, rev):
1815 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1816 try:
Anthony King7bdac712014-07-16 12:56:40 +01001817 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1818 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001819 except GitError:
1820 return [], []
1821 if p.Wait() != 0:
1822 return [], []
1823
1824 gitmodules_lines = []
1825 fd, temp_gitmodules_path = tempfile.mkstemp()
1826 try:
1827 os.write(fd, p.stdout)
1828 os.close(fd)
1829 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001830 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1831 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001832 if p.Wait() != 0:
1833 return [], []
1834 gitmodules_lines = p.stdout.split('\n')
1835 except GitError:
1836 return [], []
1837 finally:
1838 os.remove(temp_gitmodules_path)
1839
1840 names = set()
1841 paths = {}
1842 urls = {}
1843 for line in gitmodules_lines:
1844 if not line:
1845 continue
1846 m = re_path.match(line)
1847 if m:
1848 names.add(m.group(1))
1849 paths[m.group(1)] = m.group(2)
1850 continue
1851 m = re_url.match(line)
1852 if m:
1853 names.add(m.group(1))
1854 urls[m.group(1)] = m.group(2)
1855 continue
1856 names = sorted(names)
1857 return ([paths.get(name, '') for name in names],
1858 [urls.get(name, '') for name in names])
1859
1860 def git_ls_tree(gitdir, rev, paths):
1861 cmd = ['ls-tree', rev, '--']
1862 cmd.extend(paths)
1863 try:
Anthony King7bdac712014-07-16 12:56:40 +01001864 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1865 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001866 except GitError:
1867 return []
1868 if p.Wait() != 0:
1869 return []
1870 objects = {}
1871 for line in p.stdout.split('\n'):
1872 if not line.strip():
1873 continue
1874 object_rev, object_path = line.split()[2:4]
1875 objects[object_path] = object_rev
1876 return objects
1877
1878 try:
1879 rev = self.GetRevisionId()
1880 except GitError:
1881 return []
1882 return get_submodules(self.gitdir, rev)
1883
1884 def GetDerivedSubprojects(self):
1885 result = []
1886 if not self.Exists:
1887 # If git repo does not exist yet, querying its submodules will
1888 # mess up its states; so return here.
1889 return result
1890 for rev, path, url in self._GetSubmodules():
1891 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001892 relpath, worktree, gitdir, objdir = \
1893 self.manifest.GetSubprojectPaths(self, name, path)
1894 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001895 if project:
1896 result.extend(project.GetDerivedSubprojects())
1897 continue
David James8d201162013-10-11 17:03:19 -07001898
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001899 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001900 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001901 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001902 review=self.remote.review,
1903 revision=self.remote.revision)
1904 subproject = Project(manifest=self.manifest,
1905 name=name,
1906 remote=remote,
1907 gitdir=gitdir,
1908 objdir=objdir,
1909 worktree=worktree,
1910 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001911 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001912 revisionId=rev,
1913 rebase=self.rebase,
1914 groups=self.groups,
1915 sync_c=self.sync_c,
1916 sync_s=self.sync_s,
1917 parent=self,
1918 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001919 result.append(subproject)
1920 result.extend(subproject.GetDerivedSubprojects())
1921 return result
1922
1923
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001924# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001925 def _CheckForSha1(self):
1926 try:
1927 # if revision (sha or tag) is not present then following function
1928 # throws an error.
1929 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1930 return True
1931 except GitError:
1932 # There is no such persistent revision. We have to fetch it.
1933 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001934
Julien Campergue335f5ef2013-10-16 11:02:35 +02001935 def _FetchArchive(self, tarpath, cwd=None):
1936 cmd = ['archive', '-v', '-o', tarpath]
1937 cmd.append('--remote=%s' % self.remote.url)
1938 cmd.append('--prefix=%s/' % self.relpath)
1939 cmd.append(self.revisionExpr)
1940
1941 command = GitCommand(self, cmd, cwd=cwd,
1942 capture_stdout=True,
1943 capture_stderr=True)
1944
1945 if command.Wait() != 0:
1946 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1947
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001948 def _RemoteFetch(self, name=None,
1949 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001950 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001951 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001952 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001953 no_tags=False,
1954 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001955
1956 is_sha1 = False
1957 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001958 depth = None
1959
1960 # The depth should not be used when fetching to a mirror because
1961 # it will result in a shallow repository that cannot be cloned or
1962 # fetched from.
1963 if not self.manifest.IsMirror:
1964 if self.clone_depth:
1965 depth = self.clone_depth
1966 else:
1967 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001968 # The repo project should never be synced with partial depth
1969 if self.relpath == '.repo/repo':
1970 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001971
Shawn Pearce69e04d82014-01-29 12:48:54 -08001972 if depth:
1973 current_branch_only = True
1974
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001975 if ID_RE.match(self.revisionExpr) is not None:
1976 is_sha1 = True
1977
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001978 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001979 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001980 # this is a tag and its sha1 value should never change
1981 tag_name = self.revisionExpr[len(R_TAGS):]
1982
1983 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001984 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001985 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001986 if is_sha1 and not depth:
1987 # When syncing a specific commit and --depth is not set:
1988 # * if upstream is explicitly specified and is not a sha1, fetch only
1989 # upstream as users expect only upstream to be fetch.
1990 # Note: The commit might not be in upstream in which case the sync
1991 # will fail.
1992 # * otherwise, fetch all branches to make sure we end up with the
1993 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001994 if self.upstream:
1995 current_branch_only = not ID_RE.match(self.upstream)
1996 else:
1997 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001998
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001999 if not name:
2000 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002001
2002 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002003 remote = self.GetRemote(name)
2004 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002005 ssh_proxy = True
2006
Shawn O. Pearce88443382010-10-08 10:02:09 +02002007 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002008 if alt_dir and 'objects' == os.path.basename(alt_dir):
2009 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002010 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2011 remote = self.GetRemote(name)
2012
David Pursehouse8a68ff92012-09-24 12:15:13 +09002013 all_refs = self.bare_ref.all
2014 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002015 tmp = set()
2016
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302017 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002018 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002019 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002020 all_refs[r] = ref_id
2021 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002022 continue
2023
David Pursehouse8a68ff92012-09-24 12:15:13 +09002024 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002025 continue
2026
David Pursehouse8a68ff92012-09-24 12:15:13 +09002027 r = 'refs/_alt/%s' % ref_id
2028 all_refs[r] = ref_id
2029 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002030 tmp.add(r)
2031
Shawn O. Pearce88443382010-10-08 10:02:09 +02002032 tmp_packed = ''
2033 old_packed = ''
2034
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302035 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002036 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002037 tmp_packed += line
2038 if r not in tmp:
2039 old_packed += line
2040
2041 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002042 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002043 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002044
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002045 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002046
Conley Owensf97e8382015-01-21 11:12:46 -08002047 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002048 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002049 else:
2050 # If this repo has shallow objects, then we don't know which refs have
2051 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2052 # do this with projects that don't have shallow objects, since it is less
2053 # efficient.
2054 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2055 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002056
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002057 if quiet:
2058 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002059 if not self.worktree:
2060 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002061 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002062
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002063 # If using depth then we should not get all the tags since they may
2064 # be outside of the depth.
2065 if no_tags or depth:
2066 cmd.append('--no-tags')
2067 else:
2068 cmd.append('--tags')
2069
natalie.chene8996f92015-12-29 10:53:30 +08002070 refs = []
David Pursehouse74cfd272015-10-14 10:50:15 +09002071 if prune:
2072 cmd.append('--prune')
2073
Conley Owens80b87fe2014-05-09 17:13:44 -07002074 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002075 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002076 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002077 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chene8996f92015-12-29 10:53:30 +08002078 if self.manifest.IsMirror: refs.append('refs/heads/*')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002079 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002080 spec.append('tag')
2081 spec.append(tag_name)
natalie.chene8996f92015-12-29 10:53:30 +08002082 refs.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002083
David Pursehouse403b64e2015-04-27 10:41:33 +09002084 if not self.manifest.IsMirror:
2085 branch = self.revisionExpr
natalie.chen0546a1b2016-04-09 12:17:37 +08002086 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002087 # Shallow checkout of a specific commit, fetch from that commit and not
2088 # the heads only as the commit might be deeper in the history.
natalie.chene8996f92015-12-29 10:53:30 +08002089 spec.append(branch)
David Pursehouse403b64e2015-04-27 10:41:33 +09002090 else:
2091 if is_sha1:
2092 branch = self.upstream
2093 if branch is not None and branch.strip():
2094 if not branch.startswith('refs/'):
2095 branch = R_HEADS + branch
2096 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
natalie.chene8996f92015-12-29 10:53:30 +08002097 refs.append(remote.ToLocal(branch))
Conley Owens80b87fe2014-05-09 17:13:44 -07002098 cmd.extend(spec)
2099
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002100 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002101 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002102 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002103 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002104 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002105 ok = True
2106 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002107 # If needed, run the 'git remote prune' the first time through the loop
2108 elif (not _i and
2109 "error:" in gitcmd.stderr and
2110 "git remote prune" in gitcmd.stderr):
2111 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002112 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002113 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002114 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002115 break
2116 continue
Brian Harring14a66742012-09-28 20:21:57 -07002117 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002118 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2119 # in sha1 mode, we just tried sync'ing from the upstream field; it
2120 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002121 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002122 elif ret < 0:
2123 # Git died with a signal, exit immediately
2124 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002125 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002126
2127 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002128 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002129 if old_packed != '':
2130 _lwrite(packed_refs, old_packed)
2131 else:
2132 os.remove(packed_refs)
2133 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002134
2135 if is_sha1 and current_branch_only and self.upstream:
2136 # We just synced the upstream given branch; verify we
2137 # got what we wanted, else trigger a second run of all
2138 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002139 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002140 if not depth:
2141 # Avoid infinite recursion when depth is True (since depth implies
2142 # current_branch_only)
2143 return self._RemoteFetch(name=name, current_branch_only=False,
2144 initial=False, quiet=quiet, alt_dir=alt_dir)
2145 if self.clone_depth:
2146 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002147 return self._RemoteFetch(name=name,
2148 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002149 initial=False, quiet=quiet, alt_dir=alt_dir)
natalie.chene8996f92015-12-29 10:53:30 +08002150 if self.lfs_fetch:
2151 self.__FetchLfsObjects(name, refs)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002152 return ok
2153
2154 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002155 if initial and \
2156 (self.manifest.manifestProject.config.GetString('repo.depth') or
2157 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002158 return False
2159
2160 remote = self.GetRemote(self.remote.name)
2161 bundle_url = remote.url + '/clone.bundle'
2162 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002163 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2164 'persistent-http',
2165 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002166 return False
2167
2168 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2169 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002170
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002171 exist_dst = os.path.exists(bundle_dst)
2172 exist_tmp = os.path.exists(bundle_tmp)
2173
2174 if not initial and not exist_dst and not exist_tmp:
2175 return False
2176
2177 if not exist_dst:
2178 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2179 if not exist_dst:
2180 return False
2181
2182 cmd = ['fetch']
2183 if quiet:
2184 cmd.append('--quiet')
2185 if not self.worktree:
2186 cmd.append('--update-head-ok')
2187 cmd.append(bundle_dst)
2188 for f in remote.fetch:
2189 cmd.append(str(f))
2190 cmd.append('refs/tags/*:refs/tags/*')
2191
2192 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002193 if os.path.exists(bundle_dst):
2194 os.remove(bundle_dst)
2195 if os.path.exists(bundle_tmp):
2196 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002197 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002198
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002199 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002200 if os.path.exists(dstPath):
2201 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002202
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002203 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002204 if quiet:
2205 cmd += ['--silent']
2206 if os.path.exists(tmpPath):
2207 size = os.stat(tmpPath).st_size
2208 if size >= 1024:
2209 cmd += ['--continue-at', '%d' % (size,)]
2210 else:
2211 os.remove(tmpPath)
2212 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2213 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002214 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002215 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002216 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002217 if srcUrl.startswith('persistent-'):
2218 srcUrl = srcUrl[len('persistent-'):]
2219 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002220
Dave Borowitz137d0132015-01-02 11:12:54 -08002221 if IsTrace():
2222 Trace('%s', ' '.join(cmd))
2223 try:
2224 proc = subprocess.Popen(cmd)
2225 except OSError:
2226 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002227
Dave Borowitz137d0132015-01-02 11:12:54 -08002228 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002229
Dave Borowitz137d0132015-01-02 11:12:54 -08002230 if curlret == 22:
2231 # From curl man page:
2232 # 22: HTTP page not retrieved. The requested url was not found or
2233 # returned another error with the HTTP error code being 400 or above.
2234 # This return code only appears if -f, --fail is used.
2235 if not quiet:
2236 print("Server does not provide clone.bundle; ignoring.",
2237 file=sys.stderr)
2238 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002239
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002240 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002241 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002242 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002243 return True
2244 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002245 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002246 return False
2247 else:
2248 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002249
Kris Giesingc8d882a2014-12-23 13:02:32 -08002250 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002251 try:
2252 with open(path) as f:
2253 if f.read(16) == '# v2 git bundle\n':
2254 return True
2255 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002256 if not quiet:
2257 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002258 return False
2259 except OSError:
2260 return False
2261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002262 def _Checkout(self, rev, quiet=False):
2263 cmd = ['checkout']
2264 if quiet:
2265 cmd.append('-q')
2266 cmd.append(rev)
2267 cmd.append('--')
2268 if GitCommand(self, cmd).Wait() != 0:
2269 if self._allrefs:
2270 raise GitError('%s checkout %s ' % (self.name, rev))
2271
Anthony King7bdac712014-07-16 12:56:40 +01002272 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002273 cmd = ['cherry-pick']
2274 cmd.append(rev)
2275 cmd.append('--')
2276 if GitCommand(self, cmd).Wait() != 0:
2277 if self._allrefs:
2278 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2279
Anthony King7bdac712014-07-16 12:56:40 +01002280 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002281 cmd = ['revert']
2282 cmd.append('--no-edit')
2283 cmd.append(rev)
2284 cmd.append('--')
2285 if GitCommand(self, cmd).Wait() != 0:
2286 if self._allrefs:
2287 raise GitError('%s revert %s ' % (self.name, rev))
2288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289 def _ResetHard(self, rev, quiet=True):
2290 cmd = ['reset', '--hard']
2291 if quiet:
2292 cmd.append('-q')
2293 cmd.append(rev)
2294 if GitCommand(self, cmd).Wait() != 0:
2295 raise GitError('%s reset --hard %s ' % (self.name, rev))
2296
Anthony King7bdac712014-07-16 12:56:40 +01002297 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002298 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002299 if onto is not None:
2300 cmd.extend(['--onto', onto])
2301 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002302 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002303 raise GitError('%s rebase %s ' % (self.name, upstream))
2304
Pierre Tardy3d125942012-05-04 12:18:12 +02002305 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002306 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002307 if ffonly:
2308 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002309 if GitCommand(self, cmd).Wait() != 0:
2310 raise GitError('%s merge %s ' % (self.name, head))
2311
Kevin Degiabaa7f32014-11-12 11:27:45 -07002312 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002313 init_git_dir = not os.path.exists(self.gitdir)
2314 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002315 try:
2316 # Initialize the bare repository, which contains all of the objects.
2317 if init_obj_dir:
2318 os.makedirs(self.objdir)
2319 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002320
Kevin Degib1a07b82015-07-27 13:33:43 -06002321 # If we have a separate directory to hold refs, initialize it as well.
2322 if self.objdir != self.gitdir:
2323 if init_git_dir:
2324 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002325
Kevin Degib1a07b82015-07-27 13:33:43 -06002326 if init_obj_dir or init_git_dir:
2327 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2328 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002329 try:
2330 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2331 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002332 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002333 print("Retrying clone after deleting %s" %
2334 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002335 try:
2336 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002337 if self.worktree and os.path.exists(os.path.realpath
2338 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002339 shutil.rmtree(os.path.realpath(self.worktree))
2340 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2341 except:
2342 raise e
2343 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002344
Kevin Degib1a07b82015-07-27 13:33:43 -06002345 if init_git_dir:
2346 mp = self.manifest.manifestProject
2347 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002348
Kevin Degib1a07b82015-07-27 13:33:43 -06002349 if ref_dir or mirror_git:
2350 if not mirror_git:
2351 mirror_git = os.path.join(ref_dir, self.name + '.git')
2352 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2353 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002354
Kevin Degib1a07b82015-07-27 13:33:43 -06002355 if os.path.exists(mirror_git):
2356 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002357
Kevin Degib1a07b82015-07-27 13:33:43 -06002358 elif os.path.exists(repo_git):
2359 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002360
Kevin Degib1a07b82015-07-27 13:33:43 -06002361 else:
2362 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002363
Kevin Degib1a07b82015-07-27 13:33:43 -06002364 if ref_dir:
2365 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2366 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002367
Kevin Degib1a07b82015-07-27 13:33:43 -06002368 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002369
Kevin Degib1a07b82015-07-27 13:33:43 -06002370 m = self.manifest.manifestProject.config
2371 for key in ['user.name', 'user.email']:
2372 if m.Has(key, include_defaults=False):
natalie.chen507a9362018-02-14 17:52:40 +08002373 self.config.SetString(key, m.GetString(key))
Kevin Degib1a07b82015-07-27 13:33:43 -06002374 if self.manifest.IsMirror:
2375 self.config.SetString('core.bare', 'true')
2376 else:
2377 self.config.SetString('core.bare', None)
natalie.chene8996f92015-12-29 10:53:30 +08002378
natalie.chen4e5600a2016-01-13 11:51:40 +08002379 if self.lfs_fetch:
2380 self.config.SetString('filter.lfs.clean', 'git-lfs clean %f')
2381 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge %f')
2382 self.config.SetString('filter.lfs.required', 'true')
natalie.chene8996f92015-12-29 10:53:30 +08002383
Kevin Degib1a07b82015-07-27 13:33:43 -06002384 except Exception:
2385 if init_obj_dir and os.path.exists(self.objdir):
2386 shutil.rmtree(self.objdir)
2387 if init_git_dir and os.path.exists(self.gitdir):
2388 shutil.rmtree(self.gitdir)
2389 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002390
Jimmie Westera0444582012-10-24 13:44:42 +02002391 def _UpdateHooks(self):
2392 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002393 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002394
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002395 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002396 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002397 if not os.path.exists(hooks):
2398 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002399 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002400 name = os.path.basename(stock_hook)
2401
Victor Boivie65e0f352011-04-18 11:23:29 +02002402 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002403 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002404 # Don't install a Gerrit Code Review hook if this
2405 # project does not appear to use it for reviews.
2406 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002407 # Since the manifest project is one of those, but also
2408 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002409 continue
2410
2411 dst = os.path.join(hooks, name)
2412 if os.path.islink(dst):
2413 continue
2414 if os.path.exists(dst):
2415 if filecmp.cmp(stock_hook, dst, shallow=False):
2416 os.remove(dst)
2417 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002418 _warn("%s: Not replacing locally modified %s hook",
2419 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002420 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002421 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002422 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002423 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002424 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002425 raise GitError('filesystem must support symlinks')
2426 else:
2427 raise
2428
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002430 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002431 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002432 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002433 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002434 remote.review = self.remote.review
2435 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002436
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002437 if self.worktree:
2438 remote.ResetFetch(mirror=False)
2439 else:
2440 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002441 remote.Save()
2442
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002443 def _InitMRef(self):
2444 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002445 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002446
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002447 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002448 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002449
2450 def _InitAnyMRef(self, ref):
2451 cur = self.bare_ref.symref(ref)
2452
2453 if self.revisionId:
2454 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2455 msg = 'manifest set to %s' % self.revisionId
2456 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002457 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002458 else:
2459 remote = self.GetRemote(self.remote.name)
2460 dst = remote.ToLocal(self.revisionExpr)
2461 if cur != dst:
2462 msg = 'manifest set to %s' % self.revisionExpr
2463 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002464
Kevin Degi384b3c52014-10-16 16:02:58 -06002465 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002466 symlink_files = self.shareable_files[:]
2467 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002468 if share_refs:
2469 symlink_files += self.working_tree_files
2470 symlink_dirs += self.working_tree_dirs
2471 to_symlink = symlink_files + symlink_dirs
2472 for name in set(to_symlink):
2473 dst = os.path.realpath(os.path.join(destdir, name))
2474 if os.path.lexists(dst):
2475 src = os.path.realpath(os.path.join(srcdir, name))
2476 # Fail if the links are pointing to the wrong place
2477 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002478 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002479 'work tree. If you\'re comfortable with the '
2480 'possibility of losing the work tree\'s git metadata,'
2481 ' use `repo sync --force-sync {0}` to '
2482 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002483
David James8d201162013-10-11 17:03:19 -07002484 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2485 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2486
2487 Args:
2488 gitdir: The bare git repository. Must already be initialized.
2489 dotgit: The repository you would like to initialize.
2490 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2491 Only one work tree can store refs under a given |gitdir|.
2492 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2493 This saves you the effort of initializing |dotgit| yourself.
2494 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002495 symlink_files = self.shareable_files[:]
2496 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002497 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002498 symlink_files += self.working_tree_files
2499 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002500 to_symlink = symlink_files + symlink_dirs
2501
2502 to_copy = []
2503 if copy_all:
2504 to_copy = os.listdir(gitdir)
2505
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002506 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002507 for name in set(to_copy).union(to_symlink):
2508 try:
2509 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002510 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002511
Kevin Degi384b3c52014-10-16 16:02:58 -06002512 if os.path.lexists(dst):
2513 continue
David James8d201162013-10-11 17:03:19 -07002514
2515 # If the source dir doesn't exist, create an empty dir.
2516 if name in symlink_dirs and not os.path.lexists(src):
2517 os.makedirs(src)
2518
Conley Owens80b87fe2014-05-09 17:13:44 -07002519 # If the source file doesn't exist, ensure the destination
2520 # file doesn't either.
2521 if name in symlink_files and not os.path.lexists(src):
2522 try:
2523 os.remove(dst)
2524 except OSError:
2525 pass
2526
David James8d201162013-10-11 17:03:19 -07002527 if name in to_symlink:
2528 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2529 elif copy_all and not os.path.islink(dst):
2530 if os.path.isdir(src):
2531 shutil.copytree(src, dst)
2532 elif os.path.isfile(src):
2533 shutil.copy(src, dst)
2534 except OSError as e:
2535 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002536 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002537 else:
2538 raise
2539
Kevin Degiabaa7f32014-11-12 11:27:45 -07002540 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002541 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002542 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002543 try:
2544 if init_dotgit:
2545 os.makedirs(dotgit)
2546 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2547 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002548
Kevin Degiabaa7f32014-11-12 11:27:45 -07002549 try:
2550 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2551 except GitError as e:
2552 if force_sync:
2553 try:
2554 shutil.rmtree(dotgit)
2555 return self._InitWorkTree(force_sync=False)
2556 except:
2557 raise e
2558 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002559
Kevin Degib1a07b82015-07-27 13:33:43 -06002560 if init_dotgit:
2561 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002562
Kevin Degib1a07b82015-07-27 13:33:43 -06002563 cmd = ['read-tree', '--reset', '-u']
2564 cmd.append('-v')
2565 cmd.append(HEAD)
2566 if GitCommand(self, cmd).Wait() != 0:
2567 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002568
Kevin Degib1a07b82015-07-27 13:33:43 -06002569 self._CopyAndLinkFiles()
2570 except Exception:
2571 if init_dotgit:
2572 shutil.rmtree(dotgit)
2573 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002574
2575 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002576 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002577
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002578 def _revlist(self, *args, **kw):
2579 a = []
2580 a.extend(args)
2581 a.append('--')
2582 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583
2584 @property
2585 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002586 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002588 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002589 """Get logs between two revisions of this project."""
2590 comp = '..'
2591 if rev1:
2592 revs = [rev1]
2593 if rev2:
2594 revs.extend([comp, rev2])
2595 cmd = ['log', ''.join(revs)]
2596 out = DiffColoring(self.config)
2597 if out.is_on and color:
2598 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002599 if pretty_format is not None:
2600 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002601 if oneline:
2602 cmd.append('--oneline')
2603
2604 try:
2605 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2606 if log.Wait() == 0:
2607 return log.stdout
2608 except GitError:
2609 # worktree may not exist if groups changed for example. In that case,
2610 # try in gitdir instead.
2611 if not os.path.exists(self.worktree):
2612 return self.bare_git.log(*cmd[1:])
2613 else:
2614 raise
2615 return None
2616
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002617 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2618 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002619 """Get the list of logs from this revision to given revisionId"""
2620 logs = {}
2621 selfId = self.GetRevisionId(self._allrefs)
2622 toId = toProject.GetRevisionId(toProject._allrefs)
2623
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002624 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2625 pretty_format=pretty_format)
2626 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2627 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002628 return logs
2629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002631
David James8d201162013-10-11 17:03:19 -07002632 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002633 self._project = project
2634 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002635 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002637 def LsOthers(self):
2638 p = GitCommand(self._project,
2639 ['ls-files',
2640 '-z',
2641 '--others',
2642 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002643 bare=False,
David James8d201162013-10-11 17:03:19 -07002644 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002645 capture_stdout=True,
2646 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 if p.Wait() == 0:
2648 out = p.stdout
2649 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002650 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002651 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652 return []
2653
2654 def DiffZ(self, name, *args):
2655 cmd = [name]
2656 cmd.append('-z')
2657 cmd.extend(args)
2658 p = GitCommand(self._project,
2659 cmd,
David James8d201162013-10-11 17:03:19 -07002660 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002661 bare=False,
2662 capture_stdout=True,
2663 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002664 try:
2665 out = p.process.stdout.read()
2666 r = {}
2667 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002668 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002670 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002671 info = next(out)
2672 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002673 except StopIteration:
2674 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675
2676 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678 def __init__(self, path, omode, nmode, oid, nid, state):
2679 self.path = path
2680 self.src_path = None
2681 self.old_mode = omode
2682 self.new_mode = nmode
2683 self.old_id = oid
2684 self.new_id = nid
2685
2686 if len(state) == 1:
2687 self.status = state
2688 self.level = None
2689 else:
2690 self.status = state[:1]
2691 self.level = state[1:]
2692 while self.level.startswith('0'):
2693 self.level = self.level[1:]
2694
2695 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002696 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002697 if info.status in ('R', 'C'):
2698 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002699 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700 r[info.path] = info
2701 return r
2702 finally:
2703 p.Wait()
2704
2705 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002706 if self._bare:
2707 path = os.path.join(self._project.gitdir, HEAD)
2708 else:
2709 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002710 try:
2711 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002712 except IOError as e:
2713 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002714 try:
2715 line = fd.read()
2716 finally:
2717 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302718 try:
2719 line = line.decode()
2720 except AttributeError:
2721 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002722 if line.startswith('ref: '):
2723 return line[5:-1]
2724 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725
2726 def SetHead(self, ref, message=None):
2727 cmdv = []
2728 if message is not None:
2729 cmdv.extend(['-m', message])
2730 cmdv.append(HEAD)
2731 cmdv.append(ref)
2732 self.symbolic_ref(*cmdv)
2733
2734 def DetachHead(self, new, message=None):
2735 cmdv = ['--no-deref']
2736 if message is not None:
2737 cmdv.extend(['-m', message])
2738 cmdv.append(HEAD)
2739 cmdv.append(new)
2740 self.update_ref(*cmdv)
2741
2742 def UpdateRef(self, name, new, old=None,
2743 message=None,
2744 detach=False):
2745 cmdv = []
2746 if message is not None:
2747 cmdv.extend(['-m', message])
2748 if detach:
2749 cmdv.append('--no-deref')
2750 cmdv.append(name)
2751 cmdv.append(new)
2752 if old is not None:
2753 cmdv.append(old)
2754 self.update_ref(*cmdv)
2755
2756 def DeleteRef(self, name, old=None):
2757 if not old:
2758 old = self.rev_parse(name)
2759 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002760 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002761
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002762 def rev_list(self, *args, **kw):
2763 if 'format' in kw:
2764 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2765 else:
2766 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 cmdv.extend(args)
2768 p = GitCommand(self._project,
2769 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002770 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002771 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002772 capture_stdout=True,
2773 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002774 r = []
2775 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002776 if line[-1] == '\n':
2777 line = line[:-1]
2778 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002779 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002780 raise GitError('%s rev-list %s: %s' %
2781 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002782 return r
2783
2784 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002785 """Allow arbitrary git commands using pythonic syntax.
2786
2787 This allows you to do things like:
2788 git_obj.rev_parse('HEAD')
2789
2790 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2791 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002792 Any other positional arguments will be passed to the git command, and the
2793 following keyword arguments are supported:
2794 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002795
2796 Args:
2797 name: The name of the git command to call. Any '_' characters will
2798 be replaced with '-'.
2799
2800 Returns:
2801 A callable object that will try to call git with the named command.
2802 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002803 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002804
Dave Borowitz091f8932012-10-23 17:01:04 -07002805 def runner(*args, **kwargs):
2806 cmdv = []
2807 config = kwargs.pop('config', None)
2808 for k in kwargs:
2809 raise TypeError('%s() got an unexpected keyword argument %r'
2810 % (name, k))
2811 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002812 if not git_require((1, 7, 2)):
2813 raise ValueError('cannot set config on command line for %s()'
2814 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302815 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002816 cmdv.append('-c')
2817 cmdv.append('%s=%s' % (k, v))
2818 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002819 cmdv.extend(args)
2820 p = GitCommand(self._project,
2821 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002822 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002823 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002824 capture_stdout=True,
2825 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002826 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002827 raise GitError('%s %s: %s' %
2828 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302830 try:
Conley Owensedd01512013-09-26 12:59:58 -07002831 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302832 except AttributeError:
2833 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002834 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2835 return r[:-1]
2836 return r
2837 return runner
2838
2839
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002840class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002841
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002842 def __str__(self):
2843 return 'prior sync failed; rebase still in progress'
2844
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002845
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002846class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002847
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002848 def __str__(self):
2849 return 'contains uncommitted changes'
2850
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002851
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002852class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002853
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002854 def __init__(self, project, text):
2855 self.project = project
2856 self.text = text
2857
2858 def Print(self, syncbuf):
2859 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2860 syncbuf.out.nl()
2861
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002862
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002863class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002864
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002865 def __init__(self, project, why):
2866 self.project = project
2867 self.why = why
2868
2869 def Print(self, syncbuf):
2870 syncbuf.out.fail('error: %s/: %s',
2871 self.project.relpath,
2872 str(self.why))
2873 syncbuf.out.nl()
2874
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002875
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002876class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002877
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002878 def __init__(self, project, action):
2879 self.project = project
2880 self.action = action
2881
2882 def Run(self, syncbuf):
2883 out = syncbuf.out
2884 out.project('project %s/', self.project.relpath)
2885 out.nl()
2886 try:
2887 self.action()
2888 out.nl()
2889 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002890 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002891 out.nl()
2892 return False
2893
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002894
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002895class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002896
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002897 def __init__(self, config):
2898 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002899 self.project = self.printer('header', attr='bold')
2900 self.info = self.printer('info')
2901 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002902
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002903
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002904class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002905
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002906 def __init__(self, config, detach_head=False):
2907 self._messages = []
2908 self._failures = []
2909 self._later_queue1 = []
2910 self._later_queue2 = []
2911
2912 self.out = _SyncColoring(config)
2913 self.out.redirect(sys.stderr)
2914
2915 self.detach_head = detach_head
2916 self.clean = True
2917
2918 def info(self, project, fmt, *args):
2919 self._messages.append(_InfoMessage(project, fmt % args))
2920
2921 def fail(self, project, err=None):
2922 self._failures.append(_Failure(project, err))
2923 self.clean = False
2924
2925 def later1(self, project, what):
2926 self._later_queue1.append(_Later(project, what))
2927
2928 def later2(self, project, what):
2929 self._later_queue2.append(_Later(project, what))
2930
2931 def Finish(self):
2932 self._PrintMessages()
2933 self._RunLater()
2934 self._PrintMessages()
2935 return self.clean
2936
2937 def _RunLater(self):
2938 for q in ['_later_queue1', '_later_queue2']:
2939 if not self._RunQueue(q):
2940 return
2941
2942 def _RunQueue(self, queue):
2943 for m in getattr(self, queue):
2944 if not m.Run(self):
2945 self.clean = False
2946 return False
2947 setattr(self, queue, [])
2948 return True
2949
2950 def _PrintMessages(self):
2951 for m in self._messages:
2952 m.Print(self)
2953 for m in self._failures:
2954 m.Print(self)
2955
2956 self._messages = []
2957 self._failures = []
2958
2959
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962 """A special project housed under .repo.
2963 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002964
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002965 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002967 manifest=manifest,
2968 name=name,
2969 gitdir=gitdir,
2970 objdir=gitdir,
2971 worktree=worktree,
2972 remote=RemoteSpec('origin'),
2973 relpath='.repo/%s' % name,
2974 revisionExpr='refs/heads/master',
2975 revisionId=None,
2976 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002977
2978 def PreSync(self):
2979 if self.Exists:
2980 cb = self.CurrentBranch
2981 if cb:
2982 base = self.GetBranch(cb).merge
2983 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002984 self.revisionExpr = base
2985 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986
Anthony King7bdac712014-07-16 12:56:40 +01002987 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002988 """ Prepare MetaProject for manifest branch switch
2989 """
2990
2991 # detach and delete manifest branch, allowing a new
2992 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002993 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002994 self.Sync_LocalHalf(syncbuf)
2995 syncbuf.Finish()
2996
2997 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002998 ['update-ref', '-d', 'refs/heads/default'],
2999 capture_stdout=True,
3000 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003001
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003002 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003003 def LastFetch(self):
3004 try:
3005 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3006 return os.path.getmtime(fh)
3007 except OSError:
3008 return 0
3009
3010 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003011 def HasChanges(self):
3012 """Has the remote received new commits not yet checked out?
3013 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003014 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003015 return False
3016
David Pursehouse8a68ff92012-09-24 12:15:13 +09003017 all_refs = self.bare_ref.all
3018 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003019 head = self.work_git.GetHead()
3020 if head.startswith(R_HEADS):
3021 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003022 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003023 except KeyError:
3024 head = None
3025
3026 if revid == head:
3027 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003028 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003029 return True
3030 return False