blob: 142258e4b789134c42607e6dc9a379fdfc0465ab [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,
179 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
Brian Harring435370c2012-07-28 15:37:04 -0700182 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400183 draft=draft,
184 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700186 def GetPublishedRefs(self):
187 refs = {}
188 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700189 self.branch.remote.SshReviewUrl(self.project.UserEmail),
190 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700191 for line in output.split('\n'):
192 try:
193 (sha, ref) = line.split()
194 refs[sha] = ref
195 except ValueError:
196 pass
197
198 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 def __init__(self, config):
204 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100205 self.project = self.printer('header', attr='bold')
206 self.branch = self.printer('header', attr='bold')
207 self.nobranch = self.printer('nobranch', fg='red')
208 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
Anthony King7bdac712014-07-16 12:56:40 +0100210 self.added = self.printer('added', fg='green')
211 self.changed = self.printer('changed', fg='red')
212 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
214
215class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700216
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 def __init__(self, config):
218 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100219 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221
Anthony King7bdac712014-07-16 12:56:40 +0100222class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
James W. Mills24c13082012-04-12 15:04:13 -0500224 def __init__(self, name, value, keep):
225 self.name = name
226 self.value = value
227 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229
Anthony King7bdac712014-07-16 12:56:40 +0100230class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800232 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233 self.src = src
234 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800235 self.abs_src = abssrc
236 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
238 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 src = self.abs_src
240 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 # copy file if it does not exist or is out of date
242 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
243 try:
244 # remove existing file first, since it might be read-only
245 if os.path.exists(dest):
246 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400247 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200248 dest_dir = os.path.dirname(dest)
249 if not os.path.isdir(dest_dir):
250 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 shutil.copy(src, dest)
252 # make the file read-only
253 mode = os.stat(dest)[stat.ST_MODE]
254 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
255 os.chmod(dest, mode)
256 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700257 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700259
Anthony King7bdac712014-07-16 12:56:40 +0100260class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Wink Saville4c426ef2015-06-03 08:05:17 -0700262 def __init__(self, git_worktree, src, dest, relsrc, absdest):
263 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500264 self.src = src
265 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700266 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500267 self.abs_dest = absdest
268
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500270 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700271 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 try:
273 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800274 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700275 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700277 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500278 if not os.path.isdir(dest_dir):
279 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700280 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 _error('Cannot link file %s to %s', relSrc, absDest)
283
284 def _Link(self):
285 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
286 on the src linking all of the files in the source in to the destination
287 directory.
288 """
289 # We use the absSrc to handle the situation where the current directory
290 # is not the root of the repo
291 absSrc = os.path.join(self.git_worktree, self.src)
292 if os.path.exists(absSrc):
293 # Entity exists so just a simple one to one link operation
294 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
295 else:
296 # Entity doesn't exist assume there is a wild card
297 absDestDir = self.abs_dest
298 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
299 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700300 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700301 else:
302 absSrcFiles = glob.glob(absSrc)
303 for absSrcFile in absSrcFiles:
304 # Create a releative path from source dir to destination dir
305 absSrcDir = os.path.dirname(absSrcFile)
306 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
307
308 # Get the source file name
309 srcFile = os.path.basename(absSrcFile)
310
311 # Now form the final full paths to srcFile. They will be
312 # absolute for the desintaiton and relative for the srouce.
313 absDest = os.path.join(absDestDir, srcFile)
314 relSrc = os.path.join(relSrcDir, srcFile)
315 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500316
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700317
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700318class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700319
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700320 def __init__(self,
321 name,
Anthony King7bdac712014-07-16 12:56:40 +0100322 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700323 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100324 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700325 revision=None,
326 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700327 self.name = name
328 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700329 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700330 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100331 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700332 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700334
Doug Anderson37282b42011-03-04 11:54:18 -0800335class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700336
Doug Anderson37282b42011-03-04 11:54:18 -0800337 """A RepoHook contains information about a script to run as a hook.
338
339 Hooks are used to run a python script before running an upload (for instance,
340 to run presubmit checks). Eventually, we may have hooks for other actions.
341
342 This shouldn't be confused with files in the 'repo/hooks' directory. Those
343 files are copied into each '.git/hooks' folder for each project. Repo-level
344 hooks are associated instead with repo actions.
345
346 Hooks are always python. When a hook is run, we will load the hook into the
347 interpreter and execute its main() function.
348 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Doug Anderson37282b42011-03-04 11:54:18 -0800350 def __init__(self,
351 hook_type,
352 hooks_project,
353 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400354 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800355 abort_if_user_denies=False):
356 """RepoHook constructor.
357
358 Params:
359 hook_type: A string representing the type of hook. This is also used
360 to figure out the name of the file containing the hook. For
361 example: 'pre-upload'.
362 hooks_project: The project containing the repo hooks. If you have a
363 manifest, this is manifest.repo_hooks_project. OK if this is None,
364 which will make the hook a no-op.
365 topdir: Repo's top directory (the one containing the .repo directory).
366 Scripts will run with CWD as this directory. If you have a manifest,
367 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400368 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800369 abort_if_user_denies: If True, we'll throw a HookError() if the user
370 doesn't allow us to run the hook.
371 """
372 self._hook_type = hook_type
373 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400374 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800375 self._topdir = topdir
376 self._abort_if_user_denies = abort_if_user_denies
377
378 # Store the full path to the script for convenience.
379 if self._hooks_project:
380 self._script_fullpath = os.path.join(self._hooks_project.worktree,
381 self._hook_type + '.py')
382 else:
383 self._script_fullpath = None
384
385 def _GetHash(self):
386 """Return a hash of the contents of the hooks directory.
387
388 We'll just use git to do this. This hash has the property that if anything
389 changes in the directory we will return a different has.
390
391 SECURITY CONSIDERATION:
392 This hash only represents the contents of files in the hook directory, not
393 any other files imported or called by hooks. Changes to imported files
394 can change the script behavior without affecting the hash.
395
396 Returns:
397 A string representing the hash. This will always be ASCII so that it can
398 be printed to the user easily.
399 """
400 assert self._hooks_project, "Must have hooks to calculate their hash."
401
402 # We will use the work_git object rather than just calling GetRevisionId().
403 # That gives us a hash of the latest checked in version of the files that
404 # the user will actually be executing. Specifically, GetRevisionId()
405 # doesn't appear to change even if a user checks out a different version
406 # of the hooks repo (via git checkout) nor if a user commits their own revs.
407 #
408 # NOTE: Local (non-committed) changes will not be factored into this hash.
409 # I think this is OK, since we're really only worried about warning the user
410 # about upstream changes.
411 return self._hooks_project.work_git.rev_parse('HEAD')
412
413 def _GetMustVerb(self):
414 """Return 'must' if the hook is required; 'should' if not."""
415 if self._abort_if_user_denies:
416 return 'must'
417 else:
418 return 'should'
419
420 def _CheckForHookApproval(self):
421 """Check to see whether this hook has been approved.
422
Mike Frysinger40252c22016-08-15 21:23:44 -0400423 We'll accept approval of manifest URLs if they're using secure transports.
424 This way the user can say they trust the manifest hoster. For insecure
425 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800426
427 Note that we ask permission for each individual hook even though we use
428 the hash of all hooks when detecting changes. We'd like the user to be
429 able to approve / deny each hook individually. We only use the hash of all
430 hooks because there is no other easy way to detect changes to local imports.
431
432 Returns:
433 True if this hook is approved to run; False otherwise.
434
435 Raises:
436 HookError: Raised if the user doesn't approve and abort_if_user_denies
437 was passed to the consturctor.
438 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400439 if self._ManifestUrlHasSecureScheme():
440 return self._CheckForHookApprovalManifest()
441 else:
442 return self._CheckForHookApprovalHash()
443
444 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
445 changed_prompt):
446 """Check for approval for a particular attribute and hook.
447
448 Args:
449 subkey: The git config key under [repo.hooks.<hook_type>] to store the
450 last approved string.
451 new_val: The new value to compare against the last approved one.
452 main_prompt: Message to display to the user to ask for approval.
453 changed_prompt: Message explaining why we're re-asking for approval.
454
455 Returns:
456 True if this hook is approved to run; False otherwise.
457
458 Raises:
459 HookError: Raised if the user doesn't approve and abort_if_user_denies
460 was passed to the consturctor.
461 """
Doug Anderson37282b42011-03-04 11:54:18 -0800462 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400463 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800464
Mike Frysinger40252c22016-08-15 21:23:44 -0400465 # Get the last value that the user approved for this hook; may be None.
466 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800467
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800469 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800471 # Approval matched. We're done.
472 return True
473 else:
474 # Give the user a reason why we're prompting, since they last told
475 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400476 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800477 else:
478 prompt = ''
479
480 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
481 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400482 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530483 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900484 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800485
486 # User is doing a one-time approval.
487 if response in ('y', 'yes'):
488 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400489 elif response == 'always':
490 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800491 return True
492
493 # For anything else, we'll assume no approval.
494 if self._abort_if_user_denies:
495 raise HookError('You must allow the %s hook or use --no-verify.' %
496 self._hook_type)
497
498 return False
499
Mike Frysinger40252c22016-08-15 21:23:44 -0400500 def _ManifestUrlHasSecureScheme(self):
501 """Check if the URI for the manifest is a secure transport."""
502 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
503 parse_results = urllib.parse.urlparse(self._manifest_url)
504 return parse_results.scheme in secure_schemes
505
506 def _CheckForHookApprovalManifest(self):
507 """Check whether the user has approved this manifest host.
508
509 Returns:
510 True if this hook is approved to run; False otherwise.
511 """
512 return self._CheckForHookApprovalHelper(
513 'approvedmanifest',
514 self._manifest_url,
515 'Run hook scripts from %s' % (self._manifest_url,),
516 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
517
518 def _CheckForHookApprovalHash(self):
519 """Check whether the user has approved the hooks repo.
520
521 Returns:
522 True if this hook is approved to run; False otherwise.
523 """
524 prompt = ('Repo %s run the script:\n'
525 ' %s\n'
526 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700527 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400528 return self._CheckForHookApprovalHelper(
529 'approvedhash',
530 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700531 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400532 'Scripts have changed since %s was allowed.' % (self._hook_type,))
533
Doug Anderson37282b42011-03-04 11:54:18 -0800534 def _ExecuteHook(self, **kwargs):
535 """Actually execute the given hook.
536
537 This will run the hook's 'main' function in our python interpreter.
538
539 Args:
540 kwargs: Keyword arguments to pass to the hook. These are often specific
541 to the hook type. For instance, pre-upload hooks will contain
542 a project_list.
543 """
544 # Keep sys.path and CWD stashed away so that we can always restore them
545 # upon function exit.
546 orig_path = os.getcwd()
547 orig_syspath = sys.path
548
549 try:
550 # Always run hooks with CWD as topdir.
551 os.chdir(self._topdir)
552
553 # Put the hook dir as the first item of sys.path so hooks can do
554 # relative imports. We want to replace the repo dir as [0] so
555 # hooks can't import repo files.
556 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
557
558 # Exec, storing global context in the context dict. We catch exceptions
559 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500560 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800561 try:
Anthony King70f68902014-05-05 21:15:34 +0100562 exec(compile(open(self._script_fullpath).read(),
563 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800564 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700565 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
566 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800567
568 # Running the script should have defined a main() function.
569 if 'main' not in context:
570 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
571
Doug Anderson37282b42011-03-04 11:54:18 -0800572 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
573 # We don't actually want hooks to define their main with this argument--
574 # it's there to remind them that their hook should always take **kwargs.
575 # For instance, a pre-upload hook should be defined like:
576 # def main(project_list, **kwargs):
577 #
578 # This allows us to later expand the API without breaking old hooks.
579 kwargs = kwargs.copy()
580 kwargs['hook_should_take_kwargs'] = True
581
582 # Call the main function in the hook. If the hook should cause the
583 # build to fail, it will raise an Exception. We'll catch that convert
584 # to a HookError w/ just the failing traceback.
585 try:
586 context['main'](**kwargs)
587 except Exception:
588 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700589 'above.' % (traceback.format_exc(),
590 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800591 finally:
592 # Restore sys.path and CWD.
593 sys.path = orig_syspath
594 os.chdir(orig_path)
595
596 def Run(self, user_allows_all_hooks, **kwargs):
597 """Run the hook.
598
599 If the hook doesn't exist (because there is no hooks project or because
600 this particular hook is not enabled), this is a no-op.
601
602 Args:
603 user_allows_all_hooks: If True, we will never prompt about running the
604 hook--we'll just assume it's OK to run it.
605 kwargs: Keyword arguments to pass to the hook. These are often specific
606 to the hook type. For instance, pre-upload hooks will contain
607 a project_list.
608
609 Raises:
610 HookError: If there was a problem finding the hook or the user declined
611 to run a required hook (from _CheckForHookApproval).
612 """
613 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700614 if ((not self._hooks_project) or (self._hook_type not in
615 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800616 return
617
618 # Bail with a nice error if we can't find the hook.
619 if not os.path.isfile(self._script_fullpath):
620 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
621
622 # Make sure the user is OK with running the hook.
623 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
624 return
625
626 # Run the hook with the same version of python we're using.
627 self._ExecuteHook(**kwargs)
628
629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600631 # These objects can be shared between several working trees.
632 shareable_files = ['description', 'info']
633 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
634 # These objects can only be used by a single working tree.
635 working_tree_files = ['config', 'packed-refs', 'shallow']
636 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 def __init__(self,
639 manifest,
640 name,
641 remote,
642 gitdir,
David James8d201162013-10-11 17:03:19 -0700643 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644 worktree,
645 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700646 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800647 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100648 rebase=True,
649 groups=None,
650 sync_c=False,
651 sync_s=False,
652 clone_depth=None,
653 upstream=None,
654 parent=None,
655 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900656 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700657 optimized_fetch=False,
658 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800659 """Init a Project object.
660
661 Args:
662 manifest: The XmlManifest object.
663 name: The `name` attribute of manifest.xml's project element.
664 remote: RemoteSpec object specifying its remote's properties.
665 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700666 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800667 worktree: Absolute path of git working tree.
668 relpath: Relative path of git working tree to repo's top directory.
669 revisionExpr: The `revision` attribute of manifest.xml's project element.
670 revisionId: git commit id for checking out.
671 rebase: The `rebase` attribute of manifest.xml's project element.
672 groups: The `groups` attribute of manifest.xml's project element.
673 sync_c: The `sync-c` attribute of manifest.xml's project element.
674 sync_s: The `sync-s` attribute of manifest.xml's project element.
675 upstream: The `upstream` attribute of manifest.xml's project element.
676 parent: The parent Project object.
677 is_derived: False if the project was explicitly defined in the manifest;
678 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400679 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900680 optimized_fetch: If True, when a project is set to a sha1 revision, only
681 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700682 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800683 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 self.manifest = manifest
685 self.name = name
686 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800687 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700688 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800689 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700690 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800691 else:
692 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700694 self.revisionExpr = revisionExpr
695
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700696 if revisionId is None \
697 and revisionExpr \
698 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700699 self.revisionId = revisionExpr
700 else:
701 self.revisionId = revisionId
702
Mike Pontillod3153822012-02-28 11:53:24 -0800703 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700704 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700705 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800706 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900707 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700708 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800709 self.parent = parent
710 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900711 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800712 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500716 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500717 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700718 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
719 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800721 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700722 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800723 else:
724 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700725 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700726 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700727 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400728 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700729 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730
Doug Anderson37282b42011-03-04 11:54:18 -0800731 # This will be filled in if a project is later identified to be the
732 # project containing repo hooks.
733 self.enabled_repo_hooks = []
734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800736 def Derived(self):
737 return self.is_derived
738
739 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600741 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742
743 @property
744 def CurrentBranch(self):
745 """Obtain the name of the currently checked out branch.
746 The branch name omits the 'refs/heads/' prefix.
747 None is returned if the project is on a detached HEAD.
748 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700749 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750 if b.startswith(R_HEADS):
751 return b[len(R_HEADS):]
752 return None
753
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700754 def IsRebaseInProgress(self):
755 w = self.worktree
756 g = os.path.join(w, '.git')
757 return os.path.exists(os.path.join(g, 'rebase-apply')) \
758 or os.path.exists(os.path.join(g, 'rebase-merge')) \
759 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200760
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761 def IsDirty(self, consider_untracked=True):
762 """Is the working directory modified in some way?
763 """
764 self.work_git.update_index('-q',
765 '--unmerged',
766 '--ignore-missing',
767 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900768 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 return True
770 if self.work_git.DiffZ('diff-files'):
771 return True
772 if consider_untracked and self.work_git.LsOthers():
773 return True
774 return False
775
776 _userident_name = None
777 _userident_email = None
778
779 @property
780 def UserName(self):
781 """Obtain the user's personal name.
782 """
783 if self._userident_name is None:
784 self._LoadUserIdentity()
785 return self._userident_name
786
787 @property
788 def UserEmail(self):
789 """Obtain the user's email address. This is very likely
790 to be their Gerrit login.
791 """
792 if self._userident_email is None:
793 self._LoadUserIdentity()
794 return self._userident_email
795
796 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900797 u = self.bare_git.var('GIT_COMMITTER_IDENT')
798 m = re.compile("^(.*) <([^>]*)> ").match(u)
799 if m:
800 self._userident_name = m.group(1)
801 self._userident_email = m.group(2)
802 else:
803 self._userident_name = ''
804 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700805
806 def GetRemote(self, name):
807 """Get the configuration for a single remote.
808 """
809 return self.config.GetRemote(name)
810
811 def GetBranch(self, name):
812 """Get the configuration for a single branch.
813 """
814 return self.config.GetBranch(name)
815
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700816 def GetBranches(self):
817 """Get all existing local branches.
818 """
819 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900820 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700821 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700822
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530823 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700824 if name.startswith(R_HEADS):
825 name = name[len(R_HEADS):]
826 b = self.GetBranch(name)
827 b.current = name == current
828 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900829 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700830 heads[name] = b
831
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530832 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700833 if name.startswith(R_PUB):
834 name = name[len(R_PUB):]
835 b = heads.get(name)
836 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900837 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700838
839 return heads
840
Colin Cross5acde752012-03-28 20:15:45 -0700841 def MatchesGroups(self, manifest_groups):
842 """Returns true if the manifest groups specified at init should cause
843 this project to be synced.
844 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700845 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700846
847 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700848 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700849 manifest_groups: "-group1,group2"
850 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500851
852 The special manifest group "default" will match any project that
853 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700854 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500855 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700856 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700857 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500858 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700859
Conley Owens971de8e2012-04-16 10:36:08 -0700860 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700861 for group in expanded_manifest_groups:
862 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700863 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700864 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700865 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700866
Conley Owens971de8e2012-04-16 10:36:08 -0700867 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700869# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700870 def UncommitedFiles(self, get_all=True):
871 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700873 Args:
874 get_all: a boolean, if True - get information about all different
875 uncommitted files. If False - return as soon as any kind of
876 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500877 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700878 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500879 self.work_git.update_index('-q',
880 '--unmerged',
881 '--ignore-missing',
882 '--refresh')
883 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700884 details.append("rebase in progress")
885 if not get_all:
886 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500887
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700888 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
889 if changes:
890 details.extend(changes)
891 if not get_all:
892 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500893
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700894 changes = self.work_git.DiffZ('diff-files').keys()
895 if changes:
896 details.extend(changes)
897 if not get_all:
898 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500899
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700900 changes = self.work_git.LsOthers()
901 if changes:
902 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500903
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700904 return details
905
906 def HasChanges(self):
907 """Returns true if there are uncommitted changes.
908 """
909 if self.UncommitedFiles(get_all=False):
910 return True
911 else:
912 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500913
Terence Haddock4655e812011-03-31 12:33:34 +0200914 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200916
917 Args:
918 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 """
920 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700921 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200922 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700923 print(file=output_redir)
924 print('project %s/' % self.relpath, file=output_redir)
925 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 return
927
928 self.work_git.update_index('-q',
929 '--unmerged',
930 '--ignore-missing',
931 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700932 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
934 df = self.work_git.DiffZ('diff-files')
935 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100936 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700937 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938
939 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700940 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200941 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700942 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
944 branch = self.CurrentBranch
945 if branch is None:
946 out.nobranch('(*** NO BRANCH ***)')
947 else:
948 out.branch('branch %s', branch)
949 out.nl()
950
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700951 if rb:
952 out.important('prior sync failed; rebase still in progress')
953 out.nl()
954
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700955 paths = list()
956 paths.extend(di.keys())
957 paths.extend(df.keys())
958 paths.extend(do)
959
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530960 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900961 try:
962 i = di[p]
963 except KeyError:
964 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900966 try:
967 f = df[p]
968 except KeyError:
969 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200970
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900971 if i:
972 i_status = i.status.upper()
973 else:
974 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900976 if f:
977 f_status = f.status.lower()
978 else:
979 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
981 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800982 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700983 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 else:
985 line = ' %s%s\t%s' % (i_status, f_status, p)
986
987 if i and not f:
988 out.added('%s', line)
989 elif (i and f) or (not i and f):
990 out.changed('%s', line)
991 elif not i and not f:
992 out.untracked('%s', line)
993 else:
994 out.write('%s', line)
995 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200996
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700997 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998
pelyad67872d2012-03-28 14:49:58 +0300999 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 """Prints the status of the repository to stdout.
1001 """
1002 out = DiffColoring(self.config)
1003 cmd = ['diff']
1004 if out.is_on:
1005 cmd.append('--color')
1006 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001007 if absolute_paths:
1008 cmd.append('--src-prefix=a/%s/' % self.relpath)
1009 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 cmd.append('--')
1011 p = GitCommand(self,
1012 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001013 capture_stdout=True,
1014 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 has_diff = False
1016 for line in p.process.stdout:
1017 if not has_diff:
1018 out.nl()
1019 out.project('project %s/' % self.relpath)
1020 out.nl()
1021 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001022 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 p.Wait()
1024
1025
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001026# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027
David Pursehouse8a68ff92012-09-24 12:15:13 +09001028 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 """Was the branch published (uploaded) for code review?
1030 If so, returns the SHA-1 hash of the last published
1031 state for the branch.
1032 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001033 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001034 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001035 try:
1036 return self.bare_git.rev_parse(key)
1037 except GitError:
1038 return None
1039 else:
1040 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001041 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001042 except KeyError:
1043 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044
David Pursehouse8a68ff92012-09-24 12:15:13 +09001045 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001046 """Prunes any stale published refs.
1047 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 if all_refs is None:
1049 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050 heads = set()
1051 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301052 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 if name.startswith(R_HEADS):
1054 heads.add(name)
1055 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001056 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301058 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 n = name[len(R_PUB):]
1060 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001061 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001063 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 """List any branches which can be uploaded for review.
1065 """
1066 heads = {}
1067 pubed = {}
1068
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301069 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001073 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
1075 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301076 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001077 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001079 if selected_branch and branch != selected_branch:
1080 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001082 rb = self.GetUploadableBranch(branch)
1083 if rb:
1084 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 return ready
1086
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001087 def GetUploadableBranch(self, branch_name):
1088 """Get a single uploadable branch, or None.
1089 """
1090 branch = self.GetBranch(branch_name)
1091 base = branch.LocalMerge
1092 if branch.LocalMerge:
1093 rb = ReviewableBranch(self, branch, base)
1094 if rb.commits:
1095 return rb
1096 return None
1097
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001098 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001099 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001100 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001101 draft=False,
1102 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103 """Uploads the named branch for code review.
1104 """
1105 if branch is None:
1106 branch = self.CurrentBranch
1107 if branch is None:
1108 raise GitError('not currently on a branch')
1109
1110 branch = self.GetBranch(branch)
1111 if not branch.LocalMerge:
1112 raise GitError('branch %s does not track a remote' % branch.name)
1113 if not branch.remote.review:
1114 raise GitError('remote %s has no review url' % branch.remote.name)
1115
Bryan Jacobsf609f912013-05-06 13:36:24 -04001116 if dest_branch is None:
1117 dest_branch = self.dest_branch
1118 if dest_branch is None:
1119 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120 if not dest_branch.startswith(R_HEADS):
1121 dest_branch = R_HEADS + dest_branch
1122
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001123 if not branch.remote.projectname:
1124 branch.remote.projectname = self.name
1125 branch.remote.Save()
1126
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001127 url = branch.remote.ReviewUrl(self.UserEmail)
1128 if url is None:
1129 raise UploadError('review not configured')
1130 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001131
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001132 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001133 rp = ['gerrit receive-pack']
1134 for e in people[0]:
1135 rp.append('--reviewer=%s' % sq(e))
1136 for e in people[1]:
1137 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001138 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001139
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001140 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001141
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001142 if dest_branch.startswith(R_HEADS):
1143 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001144
1145 upload_type = 'for'
1146 if draft:
1147 upload_type = 'drafts'
1148
1149 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1150 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001151 if auto_topic:
1152 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001153 if not url.startswith('ssh://'):
1154 rp = ['r=%s' % p for p in people[0]] + \
1155 ['cc=%s' % p for p in people[1]]
1156 if rp:
1157 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001158 cmd.append(ref_spec)
1159
Anthony King7bdac712014-07-16 12:56:40 +01001160 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001161 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162
1163 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1164 self.bare_git.UpdateRef(R_PUB + branch.name,
1165 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001166 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
1168
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001169# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
Julien Campergue335f5ef2013-10-16 11:02:35 +02001171 def _ExtractArchive(self, tarpath, path=None):
1172 """Extract the given tar on its current location
1173
1174 Args:
1175 - tarpath: The path to the actual tar file
1176
1177 """
1178 try:
1179 with tarfile.open(tarpath, 'r') as tar:
1180 tar.extractall(path=path)
1181 return True
1182 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001183 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001184 return False
1185
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001186 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001187 quiet=False,
1188 is_new=None,
1189 current_branch_only=False,
1190 force_sync=False,
1191 clone_bundle=True,
1192 no_tags=False,
1193 archive=False,
1194 optimized_fetch=False,
1195 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 """Perform only the network IO portion of the sync process.
1197 Local working directory/branch state is not affected.
1198 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001199 if archive and not isinstance(self, MetaProject):
1200 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001201 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001202 return False
1203
1204 name = self.relpath.replace('\\', '/')
1205 name = name.replace('/', '_')
1206 tarpath = '%s.tar' % name
1207 topdir = self.manifest.topdir
1208
1209 try:
1210 self._FetchArchive(tarpath, cwd=topdir)
1211 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001212 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001213 return False
1214
1215 # From now on, we only need absolute tarpath
1216 tarpath = os.path.join(topdir, tarpath)
1217
1218 if not self._ExtractArchive(tarpath, path=topdir):
1219 return False
1220 try:
1221 os.remove(tarpath)
1222 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001223 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001224 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001225 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001226 if is_new is None:
1227 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001228 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001229 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001230 else:
1231 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001233
1234 if is_new:
1235 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1236 try:
1237 fd = open(alt, 'rb')
1238 try:
1239 alt_dir = fd.readline().rstrip()
1240 finally:
1241 fd.close()
1242 except IOError:
1243 alt_dir = None
1244 else:
1245 alt_dir = None
1246
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001247 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001248 and alt_dir is None \
1249 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001250 is_new = False
1251
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001252 if not current_branch_only:
1253 if self.sync_c:
1254 current_branch_only = True
1255 elif not self.manifest._loaded:
1256 # Manifest cannot check defaults until it syncs.
1257 current_branch_only = False
1258 elif self.manifest.default.sync_c:
1259 current_branch_only = True
1260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001261 need_to_fetch = not (optimized_fetch and
1262 (ID_RE.match(self.revisionExpr) and
1263 self._CheckForSha1()))
1264 if (need_to_fetch and
1265 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1266 current_branch_only=current_branch_only,
1267 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001268 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001269
1270 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001271 self._InitMRef()
1272 else:
1273 self._InitMirrorHead()
1274 try:
1275 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1276 except OSError:
1277 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001279
1280 def PostRepoUpgrade(self):
1281 self._InitHooks()
1282
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001283 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001284 if self.manifest.isGitcClient:
1285 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001286 for copyfile in self.copyfiles:
1287 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001288 for linkfile in self.linkfiles:
1289 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290
Julien Camperguedd654222014-01-09 16:21:37 +01001291 def GetCommitRevisionId(self):
1292 """Get revisionId of a commit.
1293
1294 Use this method instead of GetRevisionId to get the id of the commit rather
1295 than the id of the current git object (for example, a tag)
1296
1297 """
1298 if not self.revisionExpr.startswith(R_TAGS):
1299 return self.GetRevisionId(self._allrefs)
1300
1301 try:
1302 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1303 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001304 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1305 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001306
David Pursehouse8a68ff92012-09-24 12:15:13 +09001307 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 if self.revisionId:
1309 return self.revisionId
1310
1311 rem = self.GetRemote(self.remote.name)
1312 rev = rem.ToLocal(self.revisionExpr)
1313
David Pursehouse8a68ff92012-09-24 12:15:13 +09001314 if all_refs is not None and rev in all_refs:
1315 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001316
1317 try:
1318 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1319 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001320 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1321 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001322
Kevin Degiabaa7f32014-11-12 11:27:45 -07001323 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 """Perform only the local IO portion of the sync process.
1325 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001327 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001328 all_refs = self.bare_ref.all
1329 self.CleanPublishedCache(all_refs)
1330 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001331
David Pursehouse1d947b32012-10-25 12:23:11 +09001332 def _doff():
1333 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001334 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001335
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001336 head = self.work_git.GetHead()
1337 if head.startswith(R_HEADS):
1338 branch = head[len(R_HEADS):]
1339 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001340 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001341 except KeyError:
1342 head = None
1343 else:
1344 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001346 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347 # Currently on a detached HEAD. The user is assumed to
1348 # not have any local modifications worth worrying about.
1349 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001350 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001351 syncbuf.fail(self, _PriorSyncFailedError())
1352 return
1353
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001354 if head == revid:
1355 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001356 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001357 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001358 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001359 # The copy/linkfile config may have changed.
1360 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001361 return
1362 else:
1363 lost = self._revlist(not_rev(revid), HEAD)
1364 if lost:
1365 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001368 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001369 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001370 syncbuf.fail(self, e)
1371 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001372 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001373 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001375 if head == revid:
1376 # No changes; don't do anything further.
1377 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001378 # The copy/linkfile config may have changed.
1379 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001380 return
1381
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001384 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001386 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001387 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001388 syncbuf.info(self,
1389 "leaving %s; does not track upstream",
1390 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001393 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 syncbuf.fail(self, e)
1395 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001396 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001397 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001399 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001400 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001402 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403 if not_merged:
1404 if upstream_gain:
1405 # The user has published this branch and some of those
1406 # commits are not yet merged upstream. We do not want
1407 # to rewrite the published commits so we punt.
1408 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001409 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001410 "branch %s is published (but not merged) and is now "
1411 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001412 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001413 elif pub == head:
1414 # All published commits are merged, and thus we are a
1415 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001416 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 syncbuf.later1(self, _doff)
1418 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001420 # Examine the local commits not in the remote. Find the
1421 # last one attributed to this user, if any.
1422 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001423 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001424 last_mine = None
1425 cnt_mine = 0
1426 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301427 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001428 if committer_email == self.UserEmail:
1429 last_mine = commit_id
1430 cnt_mine += 1
1431
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001432 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001433 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434
1435 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001436 syncbuf.fail(self, _DirtyError())
1437 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001438
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001439 # If the upstream switched on us, warn the user.
1440 #
1441 if branch.merge != self.revisionExpr:
1442 if branch.merge and self.revisionExpr:
1443 syncbuf.info(self,
1444 'manifest switched %s...%s',
1445 branch.merge,
1446 self.revisionExpr)
1447 elif branch.merge:
1448 syncbuf.info(self,
1449 'manifest no longer tracks %s',
1450 branch.merge)
1451
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001452 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001454 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001455 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001456 syncbuf.info(self,
1457 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001458 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001460 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001461 if not ID_RE.match(self.revisionExpr):
1462 # in case of manifest sync the revisionExpr might be a SHA1
1463 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001464 if not branch.merge.startswith('refs/'):
1465 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466 branch.Save()
1467
Mike Pontillod3153822012-02-28 11:53:24 -08001468 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001469 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001470 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001471 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001472 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001473 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001475 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001476 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001477 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001478 syncbuf.fail(self, e)
1479 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001481 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001482
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001483 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001484 # dest should already be an absolute path, but src is project relative
1485 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001486 abssrc = os.path.join(self.worktree, src)
1487 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001488
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001489 def AddLinkFile(self, src, dest, absdest):
1490 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001491 # make src relative path to dest
1492 absdestdir = os.path.dirname(absdest)
1493 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001494 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001495
James W. Mills24c13082012-04-12 15:04:13 -05001496 def AddAnnotation(self, name, value, keep):
1497 self.annotations.append(_Annotation(name, value, keep))
1498
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001499 def DownloadPatchSet(self, change_id, patch_id):
1500 """Download a single patch set of a single change to FETCH_HEAD.
1501 """
1502 remote = self.GetRemote(self.remote.name)
1503
1504 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001505 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001506 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001507 if GitCommand(self, cmd, bare=True).Wait() != 0:
1508 return None
1509 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001510 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001511 change_id,
1512 patch_id,
1513 self.bare_git.rev_parse('FETCH_HEAD'))
1514
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001516# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517
Simran Basib9a1b732015-08-20 12:19:28 -07001518 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001519 """Create a new branch off the manifest's revision.
1520 """
Simran Basib9a1b732015-08-20 12:19:28 -07001521 if not branch_merge:
1522 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001523 head = self.work_git.GetHead()
1524 if head == (R_HEADS + name):
1525 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526
David Pursehouse8a68ff92012-09-24 12:15:13 +09001527 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001528 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001529 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001530 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001531 capture_stdout=True,
1532 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001533
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001534 branch = self.GetBranch(name)
1535 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001536 branch.merge = branch_merge
1537 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1538 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001539 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001540
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001541 if head.startswith(R_HEADS):
1542 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001543 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001544 except KeyError:
1545 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001546 if revid and head and revid == head:
1547 ref = os.path.join(self.gitdir, R_HEADS + name)
1548 try:
1549 os.makedirs(os.path.dirname(ref))
1550 except OSError:
1551 pass
1552 _lwrite(ref, '%s\n' % revid)
1553 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1554 'ref: %s%s\n' % (R_HEADS, name))
1555 branch.Save()
1556 return True
1557
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001558 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001559 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001560 capture_stdout=True,
1561 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001562 branch.Save()
1563 return True
1564 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565
Wink Saville02d79452009-04-10 13:01:24 -07001566 def CheckoutBranch(self, name):
1567 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001568
1569 Args:
1570 name: The name of the branch to checkout.
1571
1572 Returns:
1573 True if the checkout succeeded; False if it didn't; None if the branch
1574 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001575 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001576 rev = R_HEADS + name
1577 head = self.work_git.GetHead()
1578 if head == rev:
1579 # Already on the branch
1580 #
1581 return True
Wink Saville02d79452009-04-10 13:01:24 -07001582
David Pursehouse8a68ff92012-09-24 12:15:13 +09001583 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001584 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001585 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001586 except KeyError:
1587 # Branch does not exist in this project
1588 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001589 return None
Wink Saville02d79452009-04-10 13:01:24 -07001590
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001591 if head.startswith(R_HEADS):
1592 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001593 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001594 except KeyError:
1595 head = None
1596
1597 if head == revid:
1598 # Same revision; just update HEAD to point to the new
1599 # target branch, but otherwise take no other action.
1600 #
1601 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1602 'ref: %s%s\n' % (R_HEADS, name))
1603 return True
1604
1605 return GitCommand(self,
1606 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001607 capture_stdout=True,
1608 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001609
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001610 def AbandonBranch(self, name):
1611 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001612
1613 Args:
1614 name: The name of the branch to abandon.
1615
1616 Returns:
1617 True if the abandon succeeded; False if it didn't; None if the branch
1618 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001619 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001620 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001621 all_refs = self.bare_ref.all
1622 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001623 # Doesn't exist
1624 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001625
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001626 head = self.work_git.GetHead()
1627 if head == rev:
1628 # We can't destroy the branch while we are sitting
1629 # on it. Switch to a detached HEAD.
1630 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001631 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001632
David Pursehouse8a68ff92012-09-24 12:15:13 +09001633 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001634 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001635 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1636 '%s\n' % revid)
1637 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001638 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001639
1640 return GitCommand(self,
1641 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001642 capture_stdout=True,
1643 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645 def PruneHeads(self):
1646 """Prune any topic branches already merged into upstream.
1647 """
1648 cb = self.CurrentBranch
1649 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001650 left = self._allrefs
1651 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652 if name.startswith(R_HEADS):
1653 name = name[len(R_HEADS):]
1654 if cb is None or name != cb:
1655 kill.append(name)
1656
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001657 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001658 if cb is not None \
1659 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001660 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661 self.work_git.DetachHead(HEAD)
1662 kill.append(cb)
1663
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001665 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001666
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 try:
1668 self.bare_git.DetachHead(rev)
1669
1670 b = ['branch', '-d']
1671 b.extend(kill)
1672 b = GitCommand(self, b, bare=True,
1673 capture_stdout=True,
1674 capture_stderr=True)
1675 b.Wait()
1676 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001677 if ID_RE.match(old):
1678 self.bare_git.DetachHead(old)
1679 else:
1680 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001681 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001683 for branch in kill:
1684 if (R_HEADS + branch) not in left:
1685 self.CleanPublishedCache()
1686 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687
1688 if cb and cb not in kill:
1689 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001690 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
1692 kept = []
1693 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001694 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695 branch = self.GetBranch(branch)
1696 base = branch.LocalMerge
1697 if not base:
1698 base = rev
1699 kept.append(ReviewableBranch(self, branch, base))
1700 return kept
1701
1702
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001703# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001704
1705 def GetRegisteredSubprojects(self):
1706 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001707
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001708 def rec(subprojects):
1709 if not subprojects:
1710 return
1711 result.extend(subprojects)
1712 for p in subprojects:
1713 rec(p.subprojects)
1714 rec(self.subprojects)
1715 return result
1716
1717 def _GetSubmodules(self):
1718 # Unfortunately we cannot call `git submodule status --recursive` here
1719 # because the working tree might not exist yet, and it cannot be used
1720 # without a working tree in its current implementation.
1721
1722 def get_submodules(gitdir, rev):
1723 # Parse .gitmodules for submodule sub_paths and sub_urls
1724 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1725 if not sub_paths:
1726 return []
1727 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1728 # revision of submodule repository
1729 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1730 submodules = []
1731 for sub_path, sub_url in zip(sub_paths, sub_urls):
1732 try:
1733 sub_rev = sub_revs[sub_path]
1734 except KeyError:
1735 # Ignore non-exist submodules
1736 continue
1737 submodules.append((sub_rev, sub_path, sub_url))
1738 return submodules
1739
1740 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1741 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001742
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001743 def parse_gitmodules(gitdir, rev):
1744 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1745 try:
Anthony King7bdac712014-07-16 12:56:40 +01001746 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1747 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001748 except GitError:
1749 return [], []
1750 if p.Wait() != 0:
1751 return [], []
1752
1753 gitmodules_lines = []
1754 fd, temp_gitmodules_path = tempfile.mkstemp()
1755 try:
1756 os.write(fd, p.stdout)
1757 os.close(fd)
1758 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001759 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1760 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001761 if p.Wait() != 0:
1762 return [], []
1763 gitmodules_lines = p.stdout.split('\n')
1764 except GitError:
1765 return [], []
1766 finally:
1767 os.remove(temp_gitmodules_path)
1768
1769 names = set()
1770 paths = {}
1771 urls = {}
1772 for line in gitmodules_lines:
1773 if not line:
1774 continue
1775 m = re_path.match(line)
1776 if m:
1777 names.add(m.group(1))
1778 paths[m.group(1)] = m.group(2)
1779 continue
1780 m = re_url.match(line)
1781 if m:
1782 names.add(m.group(1))
1783 urls[m.group(1)] = m.group(2)
1784 continue
1785 names = sorted(names)
1786 return ([paths.get(name, '') for name in names],
1787 [urls.get(name, '') for name in names])
1788
1789 def git_ls_tree(gitdir, rev, paths):
1790 cmd = ['ls-tree', rev, '--']
1791 cmd.extend(paths)
1792 try:
Anthony King7bdac712014-07-16 12:56:40 +01001793 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1794 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001795 except GitError:
1796 return []
1797 if p.Wait() != 0:
1798 return []
1799 objects = {}
1800 for line in p.stdout.split('\n'):
1801 if not line.strip():
1802 continue
1803 object_rev, object_path = line.split()[2:4]
1804 objects[object_path] = object_rev
1805 return objects
1806
1807 try:
1808 rev = self.GetRevisionId()
1809 except GitError:
1810 return []
1811 return get_submodules(self.gitdir, rev)
1812
1813 def GetDerivedSubprojects(self):
1814 result = []
1815 if not self.Exists:
1816 # If git repo does not exist yet, querying its submodules will
1817 # mess up its states; so return here.
1818 return result
1819 for rev, path, url in self._GetSubmodules():
1820 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001821 relpath, worktree, gitdir, objdir = \
1822 self.manifest.GetSubprojectPaths(self, name, path)
1823 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001824 if project:
1825 result.extend(project.GetDerivedSubprojects())
1826 continue
David James8d201162013-10-11 17:03:19 -07001827
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001828 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001829 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001830 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001831 review=self.remote.review,
1832 revision=self.remote.revision)
1833 subproject = Project(manifest=self.manifest,
1834 name=name,
1835 remote=remote,
1836 gitdir=gitdir,
1837 objdir=objdir,
1838 worktree=worktree,
1839 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001840 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001841 revisionId=rev,
1842 rebase=self.rebase,
1843 groups=self.groups,
1844 sync_c=self.sync_c,
1845 sync_s=self.sync_s,
1846 parent=self,
1847 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001848 result.append(subproject)
1849 result.extend(subproject.GetDerivedSubprojects())
1850 return result
1851
1852
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001853# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001854 def _CheckForSha1(self):
1855 try:
1856 # if revision (sha or tag) is not present then following function
1857 # throws an error.
1858 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1859 return True
1860 except GitError:
1861 # There is no such persistent revision. We have to fetch it.
1862 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863
Julien Campergue335f5ef2013-10-16 11:02:35 +02001864 def _FetchArchive(self, tarpath, cwd=None):
1865 cmd = ['archive', '-v', '-o', tarpath]
1866 cmd.append('--remote=%s' % self.remote.url)
1867 cmd.append('--prefix=%s/' % self.relpath)
1868 cmd.append(self.revisionExpr)
1869
1870 command = GitCommand(self, cmd, cwd=cwd,
1871 capture_stdout=True,
1872 capture_stderr=True)
1873
1874 if command.Wait() != 0:
1875 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1876
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001877 def _RemoteFetch(self, name=None,
1878 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001879 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001880 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001881 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001882 no_tags=False,
1883 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001884
1885 is_sha1 = False
1886 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001887 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001888
David Pursehouse9bc422f2014-04-15 10:28:56 +09001889 # The depth should not be used when fetching to a mirror because
1890 # it will result in a shallow repository that cannot be cloned or
1891 # fetched from.
1892 if not self.manifest.IsMirror:
1893 if self.clone_depth:
1894 depth = self.clone_depth
1895 else:
1896 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001897 # The repo project should never be synced with partial depth
1898 if self.relpath == '.repo/repo':
1899 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001900
Shawn Pearce69e04d82014-01-29 12:48:54 -08001901 if depth:
1902 current_branch_only = True
1903
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001904 if ID_RE.match(self.revisionExpr) is not None:
1905 is_sha1 = True
1906
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001907 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001908 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001909 # this is a tag and its sha1 value should never change
1910 tag_name = self.revisionExpr[len(R_TAGS):]
1911
1912 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001913 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001914 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001915 if is_sha1 and not depth:
1916 # When syncing a specific commit and --depth is not set:
1917 # * if upstream is explicitly specified and is not a sha1, fetch only
1918 # upstream as users expect only upstream to be fetch.
1919 # Note: The commit might not be in upstream in which case the sync
1920 # will fail.
1921 # * otherwise, fetch all branches to make sure we end up with the
1922 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001923 if self.upstream:
1924 current_branch_only = not ID_RE.match(self.upstream)
1925 else:
1926 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001927
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001928 if not name:
1929 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001930
1931 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001932 remote = self.GetRemote(name)
1933 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001934 ssh_proxy = True
1935
Shawn O. Pearce88443382010-10-08 10:02:09 +02001936 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001937 if alt_dir and 'objects' == os.path.basename(alt_dir):
1938 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001939 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1940 remote = self.GetRemote(name)
1941
David Pursehouse8a68ff92012-09-24 12:15:13 +09001942 all_refs = self.bare_ref.all
1943 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001944 tmp = set()
1945
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301946 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001947 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001948 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001949 all_refs[r] = ref_id
1950 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001951 continue
1952
David Pursehouse8a68ff92012-09-24 12:15:13 +09001953 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001954 continue
1955
David Pursehouse8a68ff92012-09-24 12:15:13 +09001956 r = 'refs/_alt/%s' % ref_id
1957 all_refs[r] = ref_id
1958 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001959 tmp.add(r)
1960
Shawn O. Pearce88443382010-10-08 10:02:09 +02001961 tmp_packed = ''
1962 old_packed = ''
1963
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301964 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001965 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001966 tmp_packed += line
1967 if r not in tmp:
1968 old_packed += line
1969
1970 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001971 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001972 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001973
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001974 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001975
Conley Owensf97e8382015-01-21 11:12:46 -08001976 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001977 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001978 else:
1979 # If this repo has shallow objects, then we don't know which refs have
1980 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1981 # do this with projects that don't have shallow objects, since it is less
1982 # efficient.
1983 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1984 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001985
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001986 if quiet:
1987 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001988 if not self.worktree:
1989 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001990 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001991
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001992 # If using depth then we should not get all the tags since they may
1993 # be outside of the depth.
1994 if no_tags or depth:
1995 cmd.append('--no-tags')
1996 else:
1997 cmd.append('--tags')
1998
David Pursehouse74cfd272015-10-14 10:50:15 +09001999 if prune:
2000 cmd.append('--prune')
2001
Conley Owens80b87fe2014-05-09 17:13:44 -07002002 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002003 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002004 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002005 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002006 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002007 spec.append('tag')
2008 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002009
David Pursehouse403b64e2015-04-27 10:41:33 +09002010 if not self.manifest.IsMirror:
2011 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002012 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002013 # Shallow checkout of a specific commit, fetch from that commit and not
2014 # the heads only as the commit might be deeper in the history.
2015 spec.append(branch)
2016 else:
2017 if is_sha1:
2018 branch = self.upstream
2019 if branch is not None and branch.strip():
2020 if not branch.startswith('refs/'):
2021 branch = R_HEADS + branch
2022 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002023 cmd.extend(spec)
2024
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002025 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002026 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002027 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002028 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002029 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002030 ok = True
2031 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002032 # If needed, run the 'git remote prune' the first time through the loop
2033 elif (not _i and
2034 "error:" in gitcmd.stderr and
2035 "git remote prune" in gitcmd.stderr):
2036 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002037 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002038 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002039 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002040 break
2041 continue
Brian Harring14a66742012-09-28 20:21:57 -07002042 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002043 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2044 # in sha1 mode, we just tried sync'ing from the upstream field; it
2045 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002046 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002047 elif ret < 0:
2048 # Git died with a signal, exit immediately
2049 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002050 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002051
2052 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002053 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002054 if old_packed != '':
2055 _lwrite(packed_refs, old_packed)
2056 else:
2057 os.remove(packed_refs)
2058 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002059
2060 if is_sha1 and current_branch_only and self.upstream:
2061 # We just synced the upstream given branch; verify we
2062 # got what we wanted, else trigger a second run of all
2063 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002064 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002065 if not depth:
2066 # Avoid infinite recursion when depth is True (since depth implies
2067 # current_branch_only)
2068 return self._RemoteFetch(name=name, current_branch_only=False,
2069 initial=False, quiet=quiet, alt_dir=alt_dir)
2070 if self.clone_depth:
2071 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002072 return self._RemoteFetch(name=name,
2073 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002074 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002075
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002076 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002077
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002078 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002079 if initial and \
2080 (self.manifest.manifestProject.config.GetString('repo.depth') or
2081 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002082 return False
2083
2084 remote = self.GetRemote(self.remote.name)
2085 bundle_url = remote.url + '/clone.bundle'
2086 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002087 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2088 'persistent-http',
2089 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002090 return False
2091
2092 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2093 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2094
2095 exist_dst = os.path.exists(bundle_dst)
2096 exist_tmp = os.path.exists(bundle_tmp)
2097
2098 if not initial and not exist_dst and not exist_tmp:
2099 return False
2100
2101 if not exist_dst:
2102 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2103 if not exist_dst:
2104 return False
2105
2106 cmd = ['fetch']
2107 if quiet:
2108 cmd.append('--quiet')
2109 if not self.worktree:
2110 cmd.append('--update-head-ok')
2111 cmd.append(bundle_dst)
2112 for f in remote.fetch:
2113 cmd.append(str(f))
2114 cmd.append('refs/tags/*:refs/tags/*')
2115
2116 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002117 if os.path.exists(bundle_dst):
2118 os.remove(bundle_dst)
2119 if os.path.exists(bundle_tmp):
2120 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002121 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002123 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002124 if os.path.exists(dstPath):
2125 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002126
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002127 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002128 if quiet:
2129 cmd += ['--silent']
2130 if os.path.exists(tmpPath):
2131 size = os.stat(tmpPath).st_size
2132 if size >= 1024:
2133 cmd += ['--continue-at', '%d' % (size,)]
2134 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002135 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002136 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2137 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002138 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002139 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002140 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002141 if srcUrl.startswith('persistent-'):
2142 srcUrl = srcUrl[len('persistent-'):]
2143 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002144
Dave Borowitz137d0132015-01-02 11:12:54 -08002145 if IsTrace():
2146 Trace('%s', ' '.join(cmd))
2147 try:
2148 proc = subprocess.Popen(cmd)
2149 except OSError:
2150 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002151
Dave Borowitz137d0132015-01-02 11:12:54 -08002152 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002153
Dave Borowitz137d0132015-01-02 11:12:54 -08002154 if curlret == 22:
2155 # From curl man page:
2156 # 22: HTTP page not retrieved. The requested url was not found or
2157 # returned another error with the HTTP error code being 400 or above.
2158 # This return code only appears if -f, --fail is used.
2159 if not quiet:
2160 print("Server does not provide clone.bundle; ignoring.",
2161 file=sys.stderr)
2162 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002163
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002164 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002165 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002166 os.rename(tmpPath, dstPath)
2167 return True
2168 else:
2169 os.remove(tmpPath)
2170 return False
2171 else:
2172 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002173
Kris Giesingc8d882a2014-12-23 13:02:32 -08002174 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002175 try:
2176 with open(path) as f:
2177 if f.read(16) == '# v2 git bundle\n':
2178 return True
2179 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002180 if not quiet:
2181 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002182 return False
2183 except OSError:
2184 return False
2185
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002186 def _Checkout(self, rev, quiet=False):
2187 cmd = ['checkout']
2188 if quiet:
2189 cmd.append('-q')
2190 cmd.append(rev)
2191 cmd.append('--')
2192 if GitCommand(self, cmd).Wait() != 0:
2193 if self._allrefs:
2194 raise GitError('%s checkout %s ' % (self.name, rev))
2195
Anthony King7bdac712014-07-16 12:56:40 +01002196 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002197 cmd = ['cherry-pick']
2198 cmd.append(rev)
2199 cmd.append('--')
2200 if GitCommand(self, cmd).Wait() != 0:
2201 if self._allrefs:
2202 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2203
Anthony King7bdac712014-07-16 12:56:40 +01002204 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002205 cmd = ['revert']
2206 cmd.append('--no-edit')
2207 cmd.append(rev)
2208 cmd.append('--')
2209 if GitCommand(self, cmd).Wait() != 0:
2210 if self._allrefs:
2211 raise GitError('%s revert %s ' % (self.name, rev))
2212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002213 def _ResetHard(self, rev, quiet=True):
2214 cmd = ['reset', '--hard']
2215 if quiet:
2216 cmd.append('-q')
2217 cmd.append(rev)
2218 if GitCommand(self, cmd).Wait() != 0:
2219 raise GitError('%s reset --hard %s ' % (self.name, rev))
2220
Anthony King7bdac712014-07-16 12:56:40 +01002221 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002222 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002223 if onto is not None:
2224 cmd.extend(['--onto', onto])
2225 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002226 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002227 raise GitError('%s rebase %s ' % (self.name, upstream))
2228
Pierre Tardy3d125942012-05-04 12:18:12 +02002229 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002230 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002231 if ffonly:
2232 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002233 if GitCommand(self, cmd).Wait() != 0:
2234 raise GitError('%s merge %s ' % (self.name, head))
2235
Kevin Degiabaa7f32014-11-12 11:27:45 -07002236 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002237 init_git_dir = not os.path.exists(self.gitdir)
2238 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002239 try:
2240 # Initialize the bare repository, which contains all of the objects.
2241 if init_obj_dir:
2242 os.makedirs(self.objdir)
2243 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002244
Kevin Degib1a07b82015-07-27 13:33:43 -06002245 # If we have a separate directory to hold refs, initialize it as well.
2246 if self.objdir != self.gitdir:
2247 if init_git_dir:
2248 os.makedirs(self.gitdir)
2249
2250 if init_obj_dir or init_git_dir:
2251 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2252 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002253 try:
2254 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2255 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002256 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002257 print("Retrying clone after deleting %s" %
2258 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002259 try:
2260 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002261 if self.worktree and os.path.exists(os.path.realpath
2262 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002263 shutil.rmtree(os.path.realpath(self.worktree))
2264 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2265 except:
2266 raise e
2267 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002268
Kevin Degi384b3c52014-10-16 16:02:58 -06002269 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002270 mp = self.manifest.manifestProject
2271 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002272
Kevin Degib1a07b82015-07-27 13:33:43 -06002273 if ref_dir or mirror_git:
2274 if not mirror_git:
2275 mirror_git = os.path.join(ref_dir, self.name + '.git')
2276 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2277 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002278
Kevin Degib1a07b82015-07-27 13:33:43 -06002279 if os.path.exists(mirror_git):
2280 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002281
Kevin Degib1a07b82015-07-27 13:33:43 -06002282 elif os.path.exists(repo_git):
2283 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002284
Kevin Degib1a07b82015-07-27 13:33:43 -06002285 else:
2286 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002287
Kevin Degib1a07b82015-07-27 13:33:43 -06002288 if ref_dir:
2289 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2290 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002291
Kevin Degib1a07b82015-07-27 13:33:43 -06002292 self._UpdateHooks()
2293
2294 m = self.manifest.manifestProject.config
2295 for key in ['user.name', 'user.email']:
2296 if m.Has(key, include_defaults=False):
2297 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002298 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002299 if self.manifest.IsMirror:
2300 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002301 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002302 self.config.SetString('core.bare', None)
2303 except Exception:
2304 if init_obj_dir and os.path.exists(self.objdir):
2305 shutil.rmtree(self.objdir)
2306 if init_git_dir and os.path.exists(self.gitdir):
2307 shutil.rmtree(self.gitdir)
2308 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002309
Jimmie Westera0444582012-10-24 13:44:42 +02002310 def _UpdateHooks(self):
2311 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002312 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002314 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002315 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002316 if not os.path.exists(hooks):
2317 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002318 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002319 name = os.path.basename(stock_hook)
2320
Victor Boivie65e0f352011-04-18 11:23:29 +02002321 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002322 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002323 # Don't install a Gerrit Code Review hook if this
2324 # project does not appear to use it for reviews.
2325 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002326 # Since the manifest project is one of those, but also
2327 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002328 continue
2329
2330 dst = os.path.join(hooks, name)
2331 if os.path.islink(dst):
2332 continue
2333 if os.path.exists(dst):
2334 if filecmp.cmp(stock_hook, dst, shallow=False):
2335 os.remove(dst)
2336 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002337 _warn("%s: Not replacing locally modified %s hook",
2338 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002339 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002340 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002341 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002342 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002343 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002344 raise GitError('filesystem must support symlinks')
2345 else:
2346 raise
2347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002349 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002350 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002351 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002352 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002353 remote.review = self.remote.review
2354 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002355
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002356 if self.worktree:
2357 remote.ResetFetch(mirror=False)
2358 else:
2359 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002360 remote.Save()
2361
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002362 def _InitMRef(self):
2363 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002364 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002365
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002366 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002367 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002368
2369 def _InitAnyMRef(self, ref):
2370 cur = self.bare_ref.symref(ref)
2371
2372 if self.revisionId:
2373 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2374 msg = 'manifest set to %s' % self.revisionId
2375 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002376 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002377 else:
2378 remote = self.GetRemote(self.remote.name)
2379 dst = remote.ToLocal(self.revisionExpr)
2380 if cur != dst:
2381 msg = 'manifest set to %s' % self.revisionExpr
2382 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002383
Kevin Degi384b3c52014-10-16 16:02:58 -06002384 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002385 symlink_files = self.shareable_files[:]
2386 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002387 if share_refs:
2388 symlink_files += self.working_tree_files
2389 symlink_dirs += self.working_tree_dirs
2390 to_symlink = symlink_files + symlink_dirs
2391 for name in set(to_symlink):
2392 dst = os.path.realpath(os.path.join(destdir, name))
2393 if os.path.lexists(dst):
2394 src = os.path.realpath(os.path.join(srcdir, name))
2395 # Fail if the links are pointing to the wrong place
2396 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002397 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002398 'work tree. If you\'re comfortable with the '
2399 'possibility of losing the work tree\'s git metadata,'
2400 ' use `repo sync --force-sync {0}` to '
2401 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002402
David James8d201162013-10-11 17:03:19 -07002403 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2404 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2405
2406 Args:
2407 gitdir: The bare git repository. Must already be initialized.
2408 dotgit: The repository you would like to initialize.
2409 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2410 Only one work tree can store refs under a given |gitdir|.
2411 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2412 This saves you the effort of initializing |dotgit| yourself.
2413 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002414 symlink_files = self.shareable_files[:]
2415 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002416 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002417 symlink_files += self.working_tree_files
2418 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002419 to_symlink = symlink_files + symlink_dirs
2420
2421 to_copy = []
2422 if copy_all:
2423 to_copy = os.listdir(gitdir)
2424
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002425 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002426 for name in set(to_copy).union(to_symlink):
2427 try:
2428 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002429 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002430
Kevin Degi384b3c52014-10-16 16:02:58 -06002431 if os.path.lexists(dst):
2432 continue
David James8d201162013-10-11 17:03:19 -07002433
2434 # If the source dir doesn't exist, create an empty dir.
2435 if name in symlink_dirs and not os.path.lexists(src):
2436 os.makedirs(src)
2437
Conley Owens80b87fe2014-05-09 17:13:44 -07002438 # If the source file doesn't exist, ensure the destination
2439 # file doesn't either.
2440 if name in symlink_files and not os.path.lexists(src):
2441 try:
2442 os.remove(dst)
2443 except OSError:
2444 pass
2445
David James8d201162013-10-11 17:03:19 -07002446 if name in to_symlink:
2447 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2448 elif copy_all and not os.path.islink(dst):
2449 if os.path.isdir(src):
2450 shutil.copytree(src, dst)
2451 elif os.path.isfile(src):
2452 shutil.copy(src, dst)
2453 except OSError as e:
2454 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002455 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002456 else:
2457 raise
2458
Kevin Degiabaa7f32014-11-12 11:27:45 -07002459 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002460 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002461 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002462 try:
2463 if init_dotgit:
2464 os.makedirs(dotgit)
2465 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2466 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467
Kevin Degiabaa7f32014-11-12 11:27:45 -07002468 try:
2469 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2470 except GitError as e:
2471 if force_sync:
2472 try:
2473 shutil.rmtree(dotgit)
2474 return self._InitWorkTree(force_sync=False)
2475 except:
2476 raise e
2477 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002478
Kevin Degib1a07b82015-07-27 13:33:43 -06002479 if init_dotgit:
2480 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002481
Kevin Degib1a07b82015-07-27 13:33:43 -06002482 cmd = ['read-tree', '--reset', '-u']
2483 cmd.append('-v')
2484 cmd.append(HEAD)
2485 if GitCommand(self, cmd).Wait() != 0:
2486 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002487
Kevin Degib1a07b82015-07-27 13:33:43 -06002488 self._CopyAndLinkFiles()
2489 except Exception:
2490 if init_dotgit:
2491 shutil.rmtree(dotgit)
2492 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002493
2494 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002495 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002496
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002497 def _revlist(self, *args, **kw):
2498 a = []
2499 a.extend(args)
2500 a.append('--')
2501 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502
2503 @property
2504 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002505 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002507 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002508 """Get logs between two revisions of this project."""
2509 comp = '..'
2510 if rev1:
2511 revs = [rev1]
2512 if rev2:
2513 revs.extend([comp, rev2])
2514 cmd = ['log', ''.join(revs)]
2515 out = DiffColoring(self.config)
2516 if out.is_on and color:
2517 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002518 if pretty_format is not None:
2519 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002520 if oneline:
2521 cmd.append('--oneline')
2522
2523 try:
2524 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2525 if log.Wait() == 0:
2526 return log.stdout
2527 except GitError:
2528 # worktree may not exist if groups changed for example. In that case,
2529 # try in gitdir instead.
2530 if not os.path.exists(self.worktree):
2531 return self.bare_git.log(*cmd[1:])
2532 else:
2533 raise
2534 return None
2535
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002536 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2537 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002538 """Get the list of logs from this revision to given revisionId"""
2539 logs = {}
2540 selfId = self.GetRevisionId(self._allrefs)
2541 toId = toProject.GetRevisionId(toProject._allrefs)
2542
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002543 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2544 pretty_format=pretty_format)
2545 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2546 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002547 return logs
2548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002550
David James8d201162013-10-11 17:03:19 -07002551 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552 self._project = project
2553 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002554 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002556 def LsOthers(self):
2557 p = GitCommand(self._project,
2558 ['ls-files',
2559 '-z',
2560 '--others',
2561 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002562 bare=False,
David James8d201162013-10-11 17:03:19 -07002563 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002564 capture_stdout=True,
2565 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566 if p.Wait() == 0:
2567 out = p.stdout
2568 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002569 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002570 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002571 return []
2572
2573 def DiffZ(self, name, *args):
2574 cmd = [name]
2575 cmd.append('-z')
2576 cmd.extend(args)
2577 p = GitCommand(self._project,
2578 cmd,
David James8d201162013-10-11 17:03:19 -07002579 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002580 bare=False,
2581 capture_stdout=True,
2582 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583 try:
2584 out = p.process.stdout.read()
2585 r = {}
2586 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002587 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002589 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002590 info = next(out)
2591 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002592 except StopIteration:
2593 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002594
2595 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002596
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597 def __init__(self, path, omode, nmode, oid, nid, state):
2598 self.path = path
2599 self.src_path = None
2600 self.old_mode = omode
2601 self.new_mode = nmode
2602 self.old_id = oid
2603 self.new_id = nid
2604
2605 if len(state) == 1:
2606 self.status = state
2607 self.level = None
2608 else:
2609 self.status = state[:1]
2610 self.level = state[1:]
2611 while self.level.startswith('0'):
2612 self.level = self.level[1:]
2613
2614 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002615 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 if info.status in ('R', 'C'):
2617 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002618 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002619 r[info.path] = info
2620 return r
2621 finally:
2622 p.Wait()
2623
2624 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002625 if self._bare:
2626 path = os.path.join(self._project.gitdir, HEAD)
2627 else:
2628 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002629 try:
2630 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002631 except IOError as e:
2632 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002633 try:
2634 line = fd.read()
2635 finally:
2636 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302637 try:
2638 line = line.decode()
2639 except AttributeError:
2640 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002641 if line.startswith('ref: '):
2642 return line[5:-1]
2643 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002644
2645 def SetHead(self, ref, message=None):
2646 cmdv = []
2647 if message is not None:
2648 cmdv.extend(['-m', message])
2649 cmdv.append(HEAD)
2650 cmdv.append(ref)
2651 self.symbolic_ref(*cmdv)
2652
2653 def DetachHead(self, new, message=None):
2654 cmdv = ['--no-deref']
2655 if message is not None:
2656 cmdv.extend(['-m', message])
2657 cmdv.append(HEAD)
2658 cmdv.append(new)
2659 self.update_ref(*cmdv)
2660
2661 def UpdateRef(self, name, new, old=None,
2662 message=None,
2663 detach=False):
2664 cmdv = []
2665 if message is not None:
2666 cmdv.extend(['-m', message])
2667 if detach:
2668 cmdv.append('--no-deref')
2669 cmdv.append(name)
2670 cmdv.append(new)
2671 if old is not None:
2672 cmdv.append(old)
2673 self.update_ref(*cmdv)
2674
2675 def DeleteRef(self, name, old=None):
2676 if not old:
2677 old = self.rev_parse(name)
2678 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002679 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002680
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002681 def rev_list(self, *args, **kw):
2682 if 'format' in kw:
2683 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2684 else:
2685 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 cmdv.extend(args)
2687 p = GitCommand(self._project,
2688 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002689 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002690 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002691 capture_stdout=True,
2692 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002693 r = []
2694 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002695 if line[-1] == '\n':
2696 line = line[:-1]
2697 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002698 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002699 raise GitError('%s rev-list %s: %s' %
2700 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002701 return r
2702
2703 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002704 """Allow arbitrary git commands using pythonic syntax.
2705
2706 This allows you to do things like:
2707 git_obj.rev_parse('HEAD')
2708
2709 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2710 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002711 Any other positional arguments will be passed to the git command, and the
2712 following keyword arguments are supported:
2713 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002714
2715 Args:
2716 name: The name of the git command to call. Any '_' characters will
2717 be replaced with '-'.
2718
2719 Returns:
2720 A callable object that will try to call git with the named command.
2721 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002722 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002723
Dave Borowitz091f8932012-10-23 17:01:04 -07002724 def runner(*args, **kwargs):
2725 cmdv = []
2726 config = kwargs.pop('config', None)
2727 for k in kwargs:
2728 raise TypeError('%s() got an unexpected keyword argument %r'
2729 % (name, k))
2730 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002731 if not git_require((1, 7, 2)):
2732 raise ValueError('cannot set config on command line for %s()'
2733 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302734 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002735 cmdv.append('-c')
2736 cmdv.append('%s=%s' % (k, v))
2737 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738 cmdv.extend(args)
2739 p = GitCommand(self._project,
2740 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002741 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002742 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002743 capture_stdout=True,
2744 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002745 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002746 raise GitError('%s %s: %s' %
2747 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302749 try:
Conley Owensedd01512013-09-26 12:59:58 -07002750 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302751 except AttributeError:
2752 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2754 return r[:-1]
2755 return r
2756 return runner
2757
2758
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002759class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002760
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002761 def __str__(self):
2762 return 'prior sync failed; rebase still in progress'
2763
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002764
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002765class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002766
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002767 def __str__(self):
2768 return 'contains uncommitted changes'
2769
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002770
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002771class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002772
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002773 def __init__(self, project, text):
2774 self.project = project
2775 self.text = text
2776
2777 def Print(self, syncbuf):
2778 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2779 syncbuf.out.nl()
2780
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002781
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002782class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002783
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002784 def __init__(self, project, why):
2785 self.project = project
2786 self.why = why
2787
2788 def Print(self, syncbuf):
2789 syncbuf.out.fail('error: %s/: %s',
2790 self.project.relpath,
2791 str(self.why))
2792 syncbuf.out.nl()
2793
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002794
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002795class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002796
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002797 def __init__(self, project, action):
2798 self.project = project
2799 self.action = action
2800
2801 def Run(self, syncbuf):
2802 out = syncbuf.out
2803 out.project('project %s/', self.project.relpath)
2804 out.nl()
2805 try:
2806 self.action()
2807 out.nl()
2808 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002809 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002810 out.nl()
2811 return False
2812
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002813
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002814class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002815
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002816 def __init__(self, config):
2817 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002818 self.project = self.printer('header', attr='bold')
2819 self.info = self.printer('info')
2820 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002821
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002822
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002823class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002824
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002825 def __init__(self, config, detach_head=False):
2826 self._messages = []
2827 self._failures = []
2828 self._later_queue1 = []
2829 self._later_queue2 = []
2830
2831 self.out = _SyncColoring(config)
2832 self.out.redirect(sys.stderr)
2833
2834 self.detach_head = detach_head
2835 self.clean = True
2836
2837 def info(self, project, fmt, *args):
2838 self._messages.append(_InfoMessage(project, fmt % args))
2839
2840 def fail(self, project, err=None):
2841 self._failures.append(_Failure(project, err))
2842 self.clean = False
2843
2844 def later1(self, project, what):
2845 self._later_queue1.append(_Later(project, what))
2846
2847 def later2(self, project, what):
2848 self._later_queue2.append(_Later(project, what))
2849
2850 def Finish(self):
2851 self._PrintMessages()
2852 self._RunLater()
2853 self._PrintMessages()
2854 return self.clean
2855
2856 def _RunLater(self):
2857 for q in ['_later_queue1', '_later_queue2']:
2858 if not self._RunQueue(q):
2859 return
2860
2861 def _RunQueue(self, queue):
2862 for m in getattr(self, queue):
2863 if not m.Run(self):
2864 self.clean = False
2865 return False
2866 setattr(self, queue, [])
2867 return True
2868
2869 def _PrintMessages(self):
2870 for m in self._messages:
2871 m.Print(self)
2872 for m in self._failures:
2873 m.Print(self)
2874
2875 self._messages = []
2876 self._failures = []
2877
2878
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002879class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002880
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 """A special project housed under .repo.
2882 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002883
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002884 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002885 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002886 manifest=manifest,
2887 name=name,
2888 gitdir=gitdir,
2889 objdir=gitdir,
2890 worktree=worktree,
2891 remote=RemoteSpec('origin'),
2892 relpath='.repo/%s' % name,
2893 revisionExpr='refs/heads/master',
2894 revisionId=None,
2895 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002896
2897 def PreSync(self):
2898 if self.Exists:
2899 cb = self.CurrentBranch
2900 if cb:
2901 base = self.GetBranch(cb).merge
2902 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002903 self.revisionExpr = base
2904 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002905
Anthony King7bdac712014-07-16 12:56:40 +01002906 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002907 """ Prepare MetaProject for manifest branch switch
2908 """
2909
2910 # detach and delete manifest branch, allowing a new
2911 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002912 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002913 self.Sync_LocalHalf(syncbuf)
2914 syncbuf.Finish()
2915
2916 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002917 ['update-ref', '-d', 'refs/heads/default'],
2918 capture_stdout=True,
2919 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002920
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002921 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002922 def LastFetch(self):
2923 try:
2924 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2925 return os.path.getmtime(fh)
2926 except OSError:
2927 return 0
2928
2929 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002930 def HasChanges(self):
2931 """Has the remote received new commits not yet checked out?
2932 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002933 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002934 return False
2935
David Pursehouse8a68ff92012-09-24 12:15:13 +09002936 all_refs = self.bare_ref.all
2937 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002938 head = self.work_git.GetHead()
2939 if head.startswith(R_HEADS):
2940 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002941 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002942 except KeyError:
2943 head = None
2944
2945 if revid == head:
2946 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002947 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948 return True
2949 return False