blob: 46e06bf8599a483466d2598fa65dfc75a1bf5460 [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,
323 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700324 revision=None,
325 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700326 self.name = name
327 self.url = url
328 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100329 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700330 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700332
Doug Anderson37282b42011-03-04 11:54:18 -0800333class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700334
Doug Anderson37282b42011-03-04 11:54:18 -0800335 """A RepoHook contains information about a script to run as a hook.
336
337 Hooks are used to run a python script before running an upload (for instance,
338 to run presubmit checks). Eventually, we may have hooks for other actions.
339
340 This shouldn't be confused with files in the 'repo/hooks' directory. Those
341 files are copied into each '.git/hooks' folder for each project. Repo-level
342 hooks are associated instead with repo actions.
343
344 Hooks are always python. When a hook is run, we will load the hook into the
345 interpreter and execute its main() function.
346 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700347
Doug Anderson37282b42011-03-04 11:54:18 -0800348 def __init__(self,
349 hook_type,
350 hooks_project,
351 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400352 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800353 abort_if_user_denies=False):
354 """RepoHook constructor.
355
356 Params:
357 hook_type: A string representing the type of hook. This is also used
358 to figure out the name of the file containing the hook. For
359 example: 'pre-upload'.
360 hooks_project: The project containing the repo hooks. If you have a
361 manifest, this is manifest.repo_hooks_project. OK if this is None,
362 which will make the hook a no-op.
363 topdir: Repo's top directory (the one containing the .repo directory).
364 Scripts will run with CWD as this directory. If you have a manifest,
365 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400366 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800367 abort_if_user_denies: If True, we'll throw a HookError() if the user
368 doesn't allow us to run the hook.
369 """
370 self._hook_type = hook_type
371 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400372 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800373 self._topdir = topdir
374 self._abort_if_user_denies = abort_if_user_denies
375
376 # Store the full path to the script for convenience.
377 if self._hooks_project:
378 self._script_fullpath = os.path.join(self._hooks_project.worktree,
379 self._hook_type + '.py')
380 else:
381 self._script_fullpath = None
382
383 def _GetHash(self):
384 """Return a hash of the contents of the hooks directory.
385
386 We'll just use git to do this. This hash has the property that if anything
387 changes in the directory we will return a different has.
388
389 SECURITY CONSIDERATION:
390 This hash only represents the contents of files in the hook directory, not
391 any other files imported or called by hooks. Changes to imported files
392 can change the script behavior without affecting the hash.
393
394 Returns:
395 A string representing the hash. This will always be ASCII so that it can
396 be printed to the user easily.
397 """
398 assert self._hooks_project, "Must have hooks to calculate their hash."
399
400 # We will use the work_git object rather than just calling GetRevisionId().
401 # That gives us a hash of the latest checked in version of the files that
402 # the user will actually be executing. Specifically, GetRevisionId()
403 # doesn't appear to change even if a user checks out a different version
404 # of the hooks repo (via git checkout) nor if a user commits their own revs.
405 #
406 # NOTE: Local (non-committed) changes will not be factored into this hash.
407 # I think this is OK, since we're really only worried about warning the user
408 # about upstream changes.
409 return self._hooks_project.work_git.rev_parse('HEAD')
410
411 def _GetMustVerb(self):
412 """Return 'must' if the hook is required; 'should' if not."""
413 if self._abort_if_user_denies:
414 return 'must'
415 else:
416 return 'should'
417
418 def _CheckForHookApproval(self):
419 """Check to see whether this hook has been approved.
420
Mike Frysinger40252c22016-08-15 21:23:44 -0400421 We'll accept approval of manifest URLs if they're using secure transports.
422 This way the user can say they trust the manifest hoster. For insecure
423 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800424
425 Note that we ask permission for each individual hook even though we use
426 the hash of all hooks when detecting changes. We'd like the user to be
427 able to approve / deny each hook individually. We only use the hash of all
428 hooks because there is no other easy way to detect changes to local imports.
429
430 Returns:
431 True if this hook is approved to run; False otherwise.
432
433 Raises:
434 HookError: Raised if the user doesn't approve and abort_if_user_denies
435 was passed to the consturctor.
436 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400437 if self._ManifestUrlHasSecureScheme():
438 return self._CheckForHookApprovalManifest()
439 else:
440 return self._CheckForHookApprovalHash()
441
442 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
443 changed_prompt):
444 """Check for approval for a particular attribute and hook.
445
446 Args:
447 subkey: The git config key under [repo.hooks.<hook_type>] to store the
448 last approved string.
449 new_val: The new value to compare against the last approved one.
450 main_prompt: Message to display to the user to ask for approval.
451 changed_prompt: Message explaining why we're re-asking for approval.
452
453 Returns:
454 True if this hook is approved to run; False otherwise.
455
456 Raises:
457 HookError: Raised if the user doesn't approve and abort_if_user_denies
458 was passed to the consturctor.
459 """
Doug Anderson37282b42011-03-04 11:54:18 -0800460 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400461 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800462
Mike Frysinger40252c22016-08-15 21:23:44 -0400463 # Get the last value that the user approved for this hook; may be None.
464 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800465
Mike Frysinger40252c22016-08-15 21:23:44 -0400466 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800467 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800469 # Approval matched. We're done.
470 return True
471 else:
472 # Give the user a reason why we're prompting, since they last told
473 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800475 else:
476 prompt = ''
477
478 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
479 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400480 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530481 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900482 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800483
484 # User is doing a one-time approval.
485 if response in ('y', 'yes'):
486 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400487 elif response == 'always':
488 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800489 return True
490
491 # For anything else, we'll assume no approval.
492 if self._abort_if_user_denies:
493 raise HookError('You must allow the %s hook or use --no-verify.' %
494 self._hook_type)
495
496 return False
497
Mike Frysinger40252c22016-08-15 21:23:44 -0400498 def _ManifestUrlHasSecureScheme(self):
499 """Check if the URI for the manifest is a secure transport."""
500 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
501 parse_results = urllib.parse.urlparse(self._manifest_url)
502 return parse_results.scheme in secure_schemes
503
504 def _CheckForHookApprovalManifest(self):
505 """Check whether the user has approved this manifest host.
506
507 Returns:
508 True if this hook is approved to run; False otherwise.
509 """
510 return self._CheckForHookApprovalHelper(
511 'approvedmanifest',
512 self._manifest_url,
513 'Run hook scripts from %s' % (self._manifest_url,),
514 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
515
516 def _CheckForHookApprovalHash(self):
517 """Check whether the user has approved the hooks repo.
518
519 Returns:
520 True if this hook is approved to run; False otherwise.
521 """
522 prompt = ('Repo %s run the script:\n'
523 ' %s\n'
524 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700525 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400526 return self._CheckForHookApprovalHelper(
527 'approvedhash',
528 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700529 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400530 'Scripts have changed since %s was allowed.' % (self._hook_type,))
531
Doug Anderson37282b42011-03-04 11:54:18 -0800532 def _ExecuteHook(self, **kwargs):
533 """Actually execute the given hook.
534
535 This will run the hook's 'main' function in our python interpreter.
536
537 Args:
538 kwargs: Keyword arguments to pass to the hook. These are often specific
539 to the hook type. For instance, pre-upload hooks will contain
540 a project_list.
541 """
542 # Keep sys.path and CWD stashed away so that we can always restore them
543 # upon function exit.
544 orig_path = os.getcwd()
545 orig_syspath = sys.path
546
547 try:
548 # Always run hooks with CWD as topdir.
549 os.chdir(self._topdir)
550
551 # Put the hook dir as the first item of sys.path so hooks can do
552 # relative imports. We want to replace the repo dir as [0] so
553 # hooks can't import repo files.
554 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
555
556 # Exec, storing global context in the context dict. We catch exceptions
557 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500558 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800559 try:
Anthony King70f68902014-05-05 21:15:34 +0100560 exec(compile(open(self._script_fullpath).read(),
561 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800562 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700563 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
564 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800565
566 # Running the script should have defined a main() function.
567 if 'main' not in context:
568 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
569
Doug Anderson37282b42011-03-04 11:54:18 -0800570 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
571 # We don't actually want hooks to define their main with this argument--
572 # it's there to remind them that their hook should always take **kwargs.
573 # For instance, a pre-upload hook should be defined like:
574 # def main(project_list, **kwargs):
575 #
576 # This allows us to later expand the API without breaking old hooks.
577 kwargs = kwargs.copy()
578 kwargs['hook_should_take_kwargs'] = True
579
580 # Call the main function in the hook. If the hook should cause the
581 # build to fail, it will raise an Exception. We'll catch that convert
582 # to a HookError w/ just the failing traceback.
583 try:
584 context['main'](**kwargs)
585 except Exception:
586 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700587 'above.' % (traceback.format_exc(),
588 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800589 finally:
590 # Restore sys.path and CWD.
591 sys.path = orig_syspath
592 os.chdir(orig_path)
593
594 def Run(self, user_allows_all_hooks, **kwargs):
595 """Run the hook.
596
597 If the hook doesn't exist (because there is no hooks project or because
598 this particular hook is not enabled), this is a no-op.
599
600 Args:
601 user_allows_all_hooks: If True, we will never prompt about running the
602 hook--we'll just assume it's OK to run it.
603 kwargs: Keyword arguments to pass to the hook. These are often specific
604 to the hook type. For instance, pre-upload hooks will contain
605 a project_list.
606
607 Raises:
608 HookError: If there was a problem finding the hook or the user declined
609 to run a required hook (from _CheckForHookApproval).
610 """
611 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700612 if ((not self._hooks_project) or (self._hook_type not in
613 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800614 return
615
616 # Bail with a nice error if we can't find the hook.
617 if not os.path.isfile(self._script_fullpath):
618 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
619
620 # Make sure the user is OK with running the hook.
621 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
622 return
623
624 # Run the hook with the same version of python we're using.
625 self._ExecuteHook(**kwargs)
626
627
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600629 # These objects can be shared between several working trees.
630 shareable_files = ['description', 'info']
631 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
632 # These objects can only be used by a single working tree.
633 working_tree_files = ['config', 'packed-refs', 'shallow']
634 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700635
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 def __init__(self,
637 manifest,
638 name,
639 remote,
640 gitdir,
David James8d201162013-10-11 17:03:19 -0700641 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 worktree,
643 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700644 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800645 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100646 rebase=True,
647 groups=None,
648 sync_c=False,
649 sync_s=False,
650 clone_depth=None,
651 upstream=None,
652 parent=None,
653 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900654 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700655 optimized_fetch=False,
656 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800657 """Init a Project object.
658
659 Args:
660 manifest: The XmlManifest object.
661 name: The `name` attribute of manifest.xml's project element.
662 remote: RemoteSpec object specifying its remote's properties.
663 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700664 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800665 worktree: Absolute path of git working tree.
666 relpath: Relative path of git working tree to repo's top directory.
667 revisionExpr: The `revision` attribute of manifest.xml's project element.
668 revisionId: git commit id for checking out.
669 rebase: The `rebase` attribute of manifest.xml's project element.
670 groups: The `groups` attribute of manifest.xml's project element.
671 sync_c: The `sync-c` attribute of manifest.xml's project element.
672 sync_s: The `sync-s` attribute of manifest.xml's project element.
673 upstream: The `upstream` attribute of manifest.xml's project element.
674 parent: The parent Project object.
675 is_derived: False if the project was explicitly defined in the manifest;
676 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400677 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900678 optimized_fetch: If True, when a project is set to a sha1 revision, only
679 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700680 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800681 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 self.manifest = manifest
683 self.name = name
684 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800685 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700686 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800687 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700688 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800689 else:
690 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700692 self.revisionExpr = revisionExpr
693
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700694 if revisionId is None \
695 and revisionExpr \
696 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700697 self.revisionId = revisionExpr
698 else:
699 self.revisionId = revisionId
700
Mike Pontillod3153822012-02-28 11:53:24 -0800701 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700702 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700703 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800704 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900705 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700706 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800707 self.parent = parent
708 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900709 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800710 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800711
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500714 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500715 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700716 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
717 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800719 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700720 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800721 else:
722 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700723 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700724 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700725 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400726 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700727 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
Doug Anderson37282b42011-03-04 11:54:18 -0800729 # This will be filled in if a project is later identified to be the
730 # project containing repo hooks.
731 self.enabled_repo_hooks = []
732
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800734 def Derived(self):
735 return self.is_derived
736
737 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600739 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740
741 @property
742 def CurrentBranch(self):
743 """Obtain the name of the currently checked out branch.
744 The branch name omits the 'refs/heads/' prefix.
745 None is returned if the project is on a detached HEAD.
746 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700747 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748 if b.startswith(R_HEADS):
749 return b[len(R_HEADS):]
750 return None
751
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700752 def IsRebaseInProgress(self):
753 w = self.worktree
754 g = os.path.join(w, '.git')
755 return os.path.exists(os.path.join(g, 'rebase-apply')) \
756 or os.path.exists(os.path.join(g, 'rebase-merge')) \
757 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 def IsDirty(self, consider_untracked=True):
760 """Is the working directory modified in some way?
761 """
762 self.work_git.update_index('-q',
763 '--unmerged',
764 '--ignore-missing',
765 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900766 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 return True
768 if self.work_git.DiffZ('diff-files'):
769 return True
770 if consider_untracked and self.work_git.LsOthers():
771 return True
772 return False
773
774 _userident_name = None
775 _userident_email = None
776
777 @property
778 def UserName(self):
779 """Obtain the user's personal name.
780 """
781 if self._userident_name is None:
782 self._LoadUserIdentity()
783 return self._userident_name
784
785 @property
786 def UserEmail(self):
787 """Obtain the user's email address. This is very likely
788 to be their Gerrit login.
789 """
790 if self._userident_email is None:
791 self._LoadUserIdentity()
792 return self._userident_email
793
794 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900795 u = self.bare_git.var('GIT_COMMITTER_IDENT')
796 m = re.compile("^(.*) <([^>]*)> ").match(u)
797 if m:
798 self._userident_name = m.group(1)
799 self._userident_email = m.group(2)
800 else:
801 self._userident_name = ''
802 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803
804 def GetRemote(self, name):
805 """Get the configuration for a single remote.
806 """
807 return self.config.GetRemote(name)
808
809 def GetBranch(self, name):
810 """Get the configuration for a single branch.
811 """
812 return self.config.GetBranch(name)
813
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700814 def GetBranches(self):
815 """Get all existing local branches.
816 """
817 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900818 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700819 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700820
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530821 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700822 if name.startswith(R_HEADS):
823 name = name[len(R_HEADS):]
824 b = self.GetBranch(name)
825 b.current = name == current
826 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700828 heads[name] = b
829
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530830 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831 if name.startswith(R_PUB):
832 name = name[len(R_PUB):]
833 b = heads.get(name)
834 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900835 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700836
837 return heads
838
Colin Cross5acde752012-03-28 20:15:45 -0700839 def MatchesGroups(self, manifest_groups):
840 """Returns true if the manifest groups specified at init should cause
841 this project to be synced.
842 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700843 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700844
845 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700846 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700847 manifest_groups: "-group1,group2"
848 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500849
850 The special manifest group "default" will match any project that
851 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700852 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500853 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700854 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700855 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500856 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700857
Conley Owens971de8e2012-04-16 10:36:08 -0700858 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700859 for group in expanded_manifest_groups:
860 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700861 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700862 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700863 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700864
Conley Owens971de8e2012-04-16 10:36:08 -0700865 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700867# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700868 def UncommitedFiles(self, get_all=True):
869 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700871 Args:
872 get_all: a boolean, if True - get information about all different
873 uncommitted files. If False - return as soon as any kind of
874 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500875 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700876 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500877 self.work_git.update_index('-q',
878 '--unmerged',
879 '--ignore-missing',
880 '--refresh')
881 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700882 details.append("rebase in progress")
883 if not get_all:
884 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500885
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700886 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
887 if changes:
888 details.extend(changes)
889 if not get_all:
890 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500891
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700892 changes = self.work_git.DiffZ('diff-files').keys()
893 if changes:
894 details.extend(changes)
895 if not get_all:
896 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500897
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700898 changes = self.work_git.LsOthers()
899 if changes:
900 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500901
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700902 return details
903
904 def HasChanges(self):
905 """Returns true if there are uncommitted changes.
906 """
907 if self.UncommitedFiles(get_all=False):
908 return True
909 else:
910 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500911
Terence Haddock4655e812011-03-31 12:33:34 +0200912 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200914
915 Args:
916 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 """
918 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700919 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200920 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700921 print(file=output_redir)
922 print('project %s/' % self.relpath, file=output_redir)
923 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 return
925
926 self.work_git.update_index('-q',
927 '--unmerged',
928 '--ignore-missing',
929 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700930 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
932 df = self.work_git.DiffZ('diff-files')
933 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100934 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700935 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936
937 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700938 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200939 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700940 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941
942 branch = self.CurrentBranch
943 if branch is None:
944 out.nobranch('(*** NO BRANCH ***)')
945 else:
946 out.branch('branch %s', branch)
947 out.nl()
948
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700949 if rb:
950 out.important('prior sync failed; rebase still in progress')
951 out.nl()
952
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 paths = list()
954 paths.extend(di.keys())
955 paths.extend(df.keys())
956 paths.extend(do)
957
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530958 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900959 try:
960 i = di[p]
961 except KeyError:
962 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900964 try:
965 f = df[p]
966 except KeyError:
967 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200968
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900969 if i:
970 i_status = i.status.upper()
971 else:
972 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900974 if f:
975 f_status = f.status.lower()
976 else:
977 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978
979 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800980 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700981 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 else:
983 line = ' %s%s\t%s' % (i_status, f_status, p)
984
985 if i and not f:
986 out.added('%s', line)
987 elif (i and f) or (not i and f):
988 out.changed('%s', line)
989 elif not i and not f:
990 out.untracked('%s', line)
991 else:
992 out.write('%s', line)
993 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200994
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700995 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
pelyad67872d2012-03-28 14:49:58 +0300997 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 """Prints the status of the repository to stdout.
999 """
1000 out = DiffColoring(self.config)
1001 cmd = ['diff']
1002 if out.is_on:
1003 cmd.append('--color')
1004 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001005 if absolute_paths:
1006 cmd.append('--src-prefix=a/%s/' % self.relpath)
1007 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 cmd.append('--')
1009 p = GitCommand(self,
1010 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001011 capture_stdout=True,
1012 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 has_diff = False
1014 for line in p.process.stdout:
1015 if not has_diff:
1016 out.nl()
1017 out.project('project %s/' % self.relpath)
1018 out.nl()
1019 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001020 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001021 p.Wait()
1022
1023
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001024# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025
David Pursehouse8a68ff92012-09-24 12:15:13 +09001026 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 """Was the branch published (uploaded) for code review?
1028 If so, returns the SHA-1 hash of the last published
1029 state for the branch.
1030 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001031 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001032 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001033 try:
1034 return self.bare_git.rev_parse(key)
1035 except GitError:
1036 return None
1037 else:
1038 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001039 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001040 except KeyError:
1041 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 """Prunes any stale published refs.
1045 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001046 if all_refs is None:
1047 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001048 heads = set()
1049 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301050 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 if name.startswith(R_HEADS):
1052 heads.add(name)
1053 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001054 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301056 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057 n = name[len(R_PUB):]
1058 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001059 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001061 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 """List any branches which can be uploaded for review.
1063 """
1064 heads = {}
1065 pubed = {}
1066
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301067 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001069 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
1073 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301074 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001075 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001077 if selected_branch and branch != selected_branch:
1078 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001080 rb = self.GetUploadableBranch(branch)
1081 if rb:
1082 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 return ready
1084
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001085 def GetUploadableBranch(self, branch_name):
1086 """Get a single uploadable branch, or None.
1087 """
1088 branch = self.GetBranch(branch_name)
1089 base = branch.LocalMerge
1090 if branch.LocalMerge:
1091 rb = ReviewableBranch(self, branch, base)
1092 if rb.commits:
1093 return rb
1094 return None
1095
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001096 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001097 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001098 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001099 draft=False,
1100 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 """Uploads the named branch for code review.
1102 """
1103 if branch is None:
1104 branch = self.CurrentBranch
1105 if branch is None:
1106 raise GitError('not currently on a branch')
1107
1108 branch = self.GetBranch(branch)
1109 if not branch.LocalMerge:
1110 raise GitError('branch %s does not track a remote' % branch.name)
1111 if not branch.remote.review:
1112 raise GitError('remote %s has no review url' % branch.remote.name)
1113
Bryan Jacobsf609f912013-05-06 13:36:24 -04001114 if dest_branch is None:
1115 dest_branch = self.dest_branch
1116 if dest_branch is None:
1117 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 if not dest_branch.startswith(R_HEADS):
1119 dest_branch = R_HEADS + dest_branch
1120
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001121 if not branch.remote.projectname:
1122 branch.remote.projectname = self.name
1123 branch.remote.Save()
1124
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001125 url = branch.remote.ReviewUrl(self.UserEmail)
1126 if url is None:
1127 raise UploadError('review not configured')
1128 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001129
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001130 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001131 rp = ['gerrit receive-pack']
1132 for e in people[0]:
1133 rp.append('--reviewer=%s' % sq(e))
1134 for e in people[1]:
1135 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001136 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001137
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001138 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001139
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001140 if dest_branch.startswith(R_HEADS):
1141 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001142
1143 upload_type = 'for'
1144 if draft:
1145 upload_type = 'drafts'
1146
1147 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1148 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001149 if auto_topic:
1150 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001151 if not url.startswith('ssh://'):
1152 rp = ['r=%s' % p for p in people[0]] + \
1153 ['cc=%s' % p for p in people[1]]
1154 if rp:
1155 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001156 cmd.append(ref_spec)
1157
Anthony King7bdac712014-07-16 12:56:40 +01001158 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001159 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
1161 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1162 self.bare_git.UpdateRef(R_PUB + branch.name,
1163 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001164 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165
1166
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001167# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168
Julien Campergue335f5ef2013-10-16 11:02:35 +02001169 def _ExtractArchive(self, tarpath, path=None):
1170 """Extract the given tar on its current location
1171
1172 Args:
1173 - tarpath: The path to the actual tar file
1174
1175 """
1176 try:
1177 with tarfile.open(tarpath, 'r') as tar:
1178 tar.extractall(path=path)
1179 return True
1180 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001181 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001182 return False
1183
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001184 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001185 quiet=False,
1186 is_new=None,
1187 current_branch_only=False,
1188 force_sync=False,
1189 clone_bundle=True,
1190 no_tags=False,
1191 archive=False,
1192 optimized_fetch=False,
1193 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 """Perform only the network IO portion of the sync process.
1195 Local working directory/branch state is not affected.
1196 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001197 if archive and not isinstance(self, MetaProject):
1198 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001199 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001200 return False
1201
1202 name = self.relpath.replace('\\', '/')
1203 name = name.replace('/', '_')
1204 tarpath = '%s.tar' % name
1205 topdir = self.manifest.topdir
1206
1207 try:
1208 self._FetchArchive(tarpath, cwd=topdir)
1209 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001210 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001211 return False
1212
1213 # From now on, we only need absolute tarpath
1214 tarpath = os.path.join(topdir, tarpath)
1215
1216 if not self._ExtractArchive(tarpath, path=topdir):
1217 return False
1218 try:
1219 os.remove(tarpath)
1220 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001221 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001222 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001223 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001224 if is_new is None:
1225 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001226 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001227 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001228 else:
1229 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001230 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001231
1232 if is_new:
1233 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1234 try:
1235 fd = open(alt, 'rb')
1236 try:
1237 alt_dir = fd.readline().rstrip()
1238 finally:
1239 fd.close()
1240 except IOError:
1241 alt_dir = None
1242 else:
1243 alt_dir = None
1244
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001245 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001246 and alt_dir is None \
1247 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001248 is_new = False
1249
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001250 if not current_branch_only:
1251 if self.sync_c:
1252 current_branch_only = True
1253 elif not self.manifest._loaded:
1254 # Manifest cannot check defaults until it syncs.
1255 current_branch_only = False
1256 elif self.manifest.default.sync_c:
1257 current_branch_only = True
1258
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001259 need_to_fetch = not (optimized_fetch and
1260 (ID_RE.match(self.revisionExpr) and
1261 self._CheckForSha1()))
1262 if (need_to_fetch and
1263 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1264 current_branch_only=current_branch_only,
1265 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001266 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001267
1268 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001269 self._InitMRef()
1270 else:
1271 self._InitMirrorHead()
1272 try:
1273 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1274 except OSError:
1275 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001276 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001277
1278 def PostRepoUpgrade(self):
1279 self._InitHooks()
1280
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001281 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001282 if self.manifest.isGitcClient:
1283 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001284 for copyfile in self.copyfiles:
1285 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001286 for linkfile in self.linkfiles:
1287 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288
Julien Camperguedd654222014-01-09 16:21:37 +01001289 def GetCommitRevisionId(self):
1290 """Get revisionId of a commit.
1291
1292 Use this method instead of GetRevisionId to get the id of the commit rather
1293 than the id of the current git object (for example, a tag)
1294
1295 """
1296 if not self.revisionExpr.startswith(R_TAGS):
1297 return self.GetRevisionId(self._allrefs)
1298
1299 try:
1300 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1301 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001302 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1303 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001304
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001306 if self.revisionId:
1307 return self.revisionId
1308
1309 rem = self.GetRemote(self.remote.name)
1310 rev = rem.ToLocal(self.revisionExpr)
1311
David Pursehouse8a68ff92012-09-24 12:15:13 +09001312 if all_refs is not None and rev in all_refs:
1313 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001314
1315 try:
1316 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1317 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001318 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1319 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001320
Kevin Degiabaa7f32014-11-12 11:27:45 -07001321 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 """Perform only the local IO portion of the sync process.
1323 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001325 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001326 all_refs = self.bare_ref.all
1327 self.CleanPublishedCache(all_refs)
1328 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001329
David Pursehouse1d947b32012-10-25 12:23:11 +09001330 def _doff():
1331 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001332 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001333
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001334 head = self.work_git.GetHead()
1335 if head.startswith(R_HEADS):
1336 branch = head[len(R_HEADS):]
1337 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001338 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001339 except KeyError:
1340 head = None
1341 else:
1342 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001344 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345 # Currently on a detached HEAD. The user is assumed to
1346 # not have any local modifications worth worrying about.
1347 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001348 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001349 syncbuf.fail(self, _PriorSyncFailedError())
1350 return
1351
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001352 if head == revid:
1353 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001354 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001355 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001356 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001357 # The copy/linkfile config may have changed.
1358 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001359 return
1360 else:
1361 lost = self._revlist(not_rev(revid), HEAD)
1362 if lost:
1363 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001364
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001366 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001367 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001368 syncbuf.fail(self, e)
1369 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001370 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001371 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001373 if head == revid:
1374 # No changes; don't do anything further.
1375 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001376 # The copy/linkfile config may have changed.
1377 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001378 return
1379
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001382 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001384 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001386 syncbuf.info(self,
1387 "leaving %s; does not track upstream",
1388 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001391 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001392 syncbuf.fail(self, e)
1393 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001394 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001395 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001397 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001398 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001400 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401 if not_merged:
1402 if upstream_gain:
1403 # The user has published this branch and some of those
1404 # commits are not yet merged upstream. We do not want
1405 # to rewrite the published commits so we punt.
1406 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001407 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001408 "branch %s is published (but not merged) and is now "
1409 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001410 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001411 elif pub == head:
1412 # All published commits are merged, and thus we are a
1413 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001414 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001415 syncbuf.later1(self, _doff)
1416 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001417
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001418 # Examine the local commits not in the remote. Find the
1419 # last one attributed to this user, if any.
1420 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001421 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001422 last_mine = None
1423 cnt_mine = 0
1424 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301425 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001426 if committer_email == self.UserEmail:
1427 last_mine = commit_id
1428 cnt_mine += 1
1429
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001430 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001431 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001432
1433 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001434 syncbuf.fail(self, _DirtyError())
1435 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001436
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001437 # If the upstream switched on us, warn the user.
1438 #
1439 if branch.merge != self.revisionExpr:
1440 if branch.merge and self.revisionExpr:
1441 syncbuf.info(self,
1442 'manifest switched %s...%s',
1443 branch.merge,
1444 self.revisionExpr)
1445 elif branch.merge:
1446 syncbuf.info(self,
1447 'manifest no longer tracks %s',
1448 branch.merge)
1449
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001450 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001451 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001452 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001454 syncbuf.info(self,
1455 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001456 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001458 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001459 if not ID_RE.match(self.revisionExpr):
1460 # in case of manifest sync the revisionExpr might be a SHA1
1461 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001462 if not branch.merge.startswith('refs/'):
1463 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001464 branch.Save()
1465
Mike Pontillod3153822012-02-28 11:53:24 -08001466 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001467 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001468 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001469 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001470 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001471 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001473 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001474 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001475 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001476 syncbuf.fail(self, e)
1477 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001479 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001481 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001482 # dest should already be an absolute path, but src is project relative
1483 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001484 abssrc = os.path.join(self.worktree, src)
1485 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001487 def AddLinkFile(self, src, dest, absdest):
1488 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001489 # make src relative path to dest
1490 absdestdir = os.path.dirname(absdest)
1491 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001492 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001493
James W. Mills24c13082012-04-12 15:04:13 -05001494 def AddAnnotation(self, name, value, keep):
1495 self.annotations.append(_Annotation(name, value, keep))
1496
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001497 def DownloadPatchSet(self, change_id, patch_id):
1498 """Download a single patch set of a single change to FETCH_HEAD.
1499 """
1500 remote = self.GetRemote(self.remote.name)
1501
1502 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001503 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001504 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001505 if GitCommand(self, cmd, bare=True).Wait() != 0:
1506 return None
1507 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001508 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001509 change_id,
1510 patch_id,
1511 self.bare_git.rev_parse('FETCH_HEAD'))
1512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001513
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001514# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515
Simran Basib9a1b732015-08-20 12:19:28 -07001516 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517 """Create a new branch off the manifest's revision.
1518 """
Simran Basib9a1b732015-08-20 12:19:28 -07001519 if not branch_merge:
1520 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001521 head = self.work_git.GetHead()
1522 if head == (R_HEADS + name):
1523 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524
David Pursehouse8a68ff92012-09-24 12:15:13 +09001525 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001526 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001527 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001528 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001529 capture_stdout=True,
1530 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001531
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001532 branch = self.GetBranch(name)
1533 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001534 branch.merge = branch_merge
1535 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1536 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001537 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001538
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001539 if head.startswith(R_HEADS):
1540 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001541 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001542 except KeyError:
1543 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001544 if revid and head and revid == head:
1545 ref = os.path.join(self.gitdir, R_HEADS + name)
1546 try:
1547 os.makedirs(os.path.dirname(ref))
1548 except OSError:
1549 pass
1550 _lwrite(ref, '%s\n' % revid)
1551 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1552 'ref: %s%s\n' % (R_HEADS, name))
1553 branch.Save()
1554 return True
1555
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001556 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001557 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001558 capture_stdout=True,
1559 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001560 branch.Save()
1561 return True
1562 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563
Wink Saville02d79452009-04-10 13:01:24 -07001564 def CheckoutBranch(self, name):
1565 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001566
1567 Args:
1568 name: The name of the branch to checkout.
1569
1570 Returns:
1571 True if the checkout succeeded; False if it didn't; None if the branch
1572 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001573 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001574 rev = R_HEADS + name
1575 head = self.work_git.GetHead()
1576 if head == rev:
1577 # Already on the branch
1578 #
1579 return True
Wink Saville02d79452009-04-10 13:01:24 -07001580
David Pursehouse8a68ff92012-09-24 12:15:13 +09001581 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001582 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001583 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001584 except KeyError:
1585 # Branch does not exist in this project
1586 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001587 return None
Wink Saville02d79452009-04-10 13:01:24 -07001588
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001589 if head.startswith(R_HEADS):
1590 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001591 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001592 except KeyError:
1593 head = None
1594
1595 if head == revid:
1596 # Same revision; just update HEAD to point to the new
1597 # target branch, but otherwise take no other action.
1598 #
1599 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1600 'ref: %s%s\n' % (R_HEADS, name))
1601 return True
1602
1603 return GitCommand(self,
1604 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001605 capture_stdout=True,
1606 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001607
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001608 def AbandonBranch(self, name):
1609 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001610
1611 Args:
1612 name: The name of the branch to abandon.
1613
1614 Returns:
1615 True if the abandon succeeded; False if it didn't; None if the branch
1616 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001617 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001618 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001619 all_refs = self.bare_ref.all
1620 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001621 # Doesn't exist
1622 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001623
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001624 head = self.work_git.GetHead()
1625 if head == rev:
1626 # We can't destroy the branch while we are sitting
1627 # on it. Switch to a detached HEAD.
1628 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001629 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001630
David Pursehouse8a68ff92012-09-24 12:15:13 +09001631 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001632 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001633 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1634 '%s\n' % revid)
1635 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001636 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001637
1638 return GitCommand(self,
1639 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001640 capture_stdout=True,
1641 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001642
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 def PruneHeads(self):
1644 """Prune any topic branches already merged into upstream.
1645 """
1646 cb = self.CurrentBranch
1647 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001648 left = self._allrefs
1649 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650 if name.startswith(R_HEADS):
1651 name = name[len(R_HEADS):]
1652 if cb is None or name != cb:
1653 kill.append(name)
1654
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001655 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 if cb is not None \
1657 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001658 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659 self.work_git.DetachHead(HEAD)
1660 kill.append(cb)
1661
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001663 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665 try:
1666 self.bare_git.DetachHead(rev)
1667
1668 b = ['branch', '-d']
1669 b.extend(kill)
1670 b = GitCommand(self, b, bare=True,
1671 capture_stdout=True,
1672 capture_stderr=True)
1673 b.Wait()
1674 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001675 if ID_RE.match(old):
1676 self.bare_git.DetachHead(old)
1677 else:
1678 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001679 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001680
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001681 for branch in kill:
1682 if (R_HEADS + branch) not in left:
1683 self.CleanPublishedCache()
1684 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685
1686 if cb and cb not in kill:
1687 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001688 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001689
1690 kept = []
1691 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001692 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693 branch = self.GetBranch(branch)
1694 base = branch.LocalMerge
1695 if not base:
1696 base = rev
1697 kept.append(ReviewableBranch(self, branch, base))
1698 return kept
1699
1700
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001701# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001702
1703 def GetRegisteredSubprojects(self):
1704 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001705
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001706 def rec(subprojects):
1707 if not subprojects:
1708 return
1709 result.extend(subprojects)
1710 for p in subprojects:
1711 rec(p.subprojects)
1712 rec(self.subprojects)
1713 return result
1714
1715 def _GetSubmodules(self):
1716 # Unfortunately we cannot call `git submodule status --recursive` here
1717 # because the working tree might not exist yet, and it cannot be used
1718 # without a working tree in its current implementation.
1719
1720 def get_submodules(gitdir, rev):
1721 # Parse .gitmodules for submodule sub_paths and sub_urls
1722 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1723 if not sub_paths:
1724 return []
1725 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1726 # revision of submodule repository
1727 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1728 submodules = []
1729 for sub_path, sub_url in zip(sub_paths, sub_urls):
1730 try:
1731 sub_rev = sub_revs[sub_path]
1732 except KeyError:
1733 # Ignore non-exist submodules
1734 continue
1735 submodules.append((sub_rev, sub_path, sub_url))
1736 return submodules
1737
1738 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1739 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001740
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001741 def parse_gitmodules(gitdir, rev):
1742 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1743 try:
Anthony King7bdac712014-07-16 12:56:40 +01001744 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1745 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001746 except GitError:
1747 return [], []
1748 if p.Wait() != 0:
1749 return [], []
1750
1751 gitmodules_lines = []
1752 fd, temp_gitmodules_path = tempfile.mkstemp()
1753 try:
1754 os.write(fd, p.stdout)
1755 os.close(fd)
1756 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001757 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1758 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001759 if p.Wait() != 0:
1760 return [], []
1761 gitmodules_lines = p.stdout.split('\n')
1762 except GitError:
1763 return [], []
1764 finally:
1765 os.remove(temp_gitmodules_path)
1766
1767 names = set()
1768 paths = {}
1769 urls = {}
1770 for line in gitmodules_lines:
1771 if not line:
1772 continue
1773 m = re_path.match(line)
1774 if m:
1775 names.add(m.group(1))
1776 paths[m.group(1)] = m.group(2)
1777 continue
1778 m = re_url.match(line)
1779 if m:
1780 names.add(m.group(1))
1781 urls[m.group(1)] = m.group(2)
1782 continue
1783 names = sorted(names)
1784 return ([paths.get(name, '') for name in names],
1785 [urls.get(name, '') for name in names])
1786
1787 def git_ls_tree(gitdir, rev, paths):
1788 cmd = ['ls-tree', rev, '--']
1789 cmd.extend(paths)
1790 try:
Anthony King7bdac712014-07-16 12:56:40 +01001791 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1792 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001793 except GitError:
1794 return []
1795 if p.Wait() != 0:
1796 return []
1797 objects = {}
1798 for line in p.stdout.split('\n'):
1799 if not line.strip():
1800 continue
1801 object_rev, object_path = line.split()[2:4]
1802 objects[object_path] = object_rev
1803 return objects
1804
1805 try:
1806 rev = self.GetRevisionId()
1807 except GitError:
1808 return []
1809 return get_submodules(self.gitdir, rev)
1810
1811 def GetDerivedSubprojects(self):
1812 result = []
1813 if not self.Exists:
1814 # If git repo does not exist yet, querying its submodules will
1815 # mess up its states; so return here.
1816 return result
1817 for rev, path, url in self._GetSubmodules():
1818 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001819 relpath, worktree, gitdir, objdir = \
1820 self.manifest.GetSubprojectPaths(self, name, path)
1821 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001822 if project:
1823 result.extend(project.GetDerivedSubprojects())
1824 continue
David James8d201162013-10-11 17:03:19 -07001825
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001826 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001827 url=url,
1828 review=self.remote.review,
1829 revision=self.remote.revision)
1830 subproject = Project(manifest=self.manifest,
1831 name=name,
1832 remote=remote,
1833 gitdir=gitdir,
1834 objdir=objdir,
1835 worktree=worktree,
1836 relpath=relpath,
1837 revisionExpr=self.revisionExpr,
1838 revisionId=rev,
1839 rebase=self.rebase,
1840 groups=self.groups,
1841 sync_c=self.sync_c,
1842 sync_s=self.sync_s,
1843 parent=self,
1844 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001845 result.append(subproject)
1846 result.extend(subproject.GetDerivedSubprojects())
1847 return result
1848
1849
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001850# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001851 def _CheckForSha1(self):
1852 try:
1853 # if revision (sha or tag) is not present then following function
1854 # throws an error.
1855 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1856 return True
1857 except GitError:
1858 # There is no such persistent revision. We have to fetch it.
1859 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001860
Julien Campergue335f5ef2013-10-16 11:02:35 +02001861 def _FetchArchive(self, tarpath, cwd=None):
1862 cmd = ['archive', '-v', '-o', tarpath]
1863 cmd.append('--remote=%s' % self.remote.url)
1864 cmd.append('--prefix=%s/' % self.relpath)
1865 cmd.append(self.revisionExpr)
1866
1867 command = GitCommand(self, cmd, cwd=cwd,
1868 capture_stdout=True,
1869 capture_stderr=True)
1870
1871 if command.Wait() != 0:
1872 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1873
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001874 def _RemoteFetch(self, name=None,
1875 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001876 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001877 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001878 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001879 no_tags=False,
1880 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001881
1882 is_sha1 = False
1883 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001884 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001885
David Pursehouse9bc422f2014-04-15 10:28:56 +09001886 # The depth should not be used when fetching to a mirror because
1887 # it will result in a shallow repository that cannot be cloned or
1888 # fetched from.
1889 if not self.manifest.IsMirror:
1890 if self.clone_depth:
1891 depth = self.clone_depth
1892 else:
1893 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001894 # The repo project should never be synced with partial depth
1895 if self.relpath == '.repo/repo':
1896 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001897
Shawn Pearce69e04d82014-01-29 12:48:54 -08001898 if depth:
1899 current_branch_only = True
1900
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001901 if ID_RE.match(self.revisionExpr) is not None:
1902 is_sha1 = True
1903
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001904 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001905 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001906 # this is a tag and its sha1 value should never change
1907 tag_name = self.revisionExpr[len(R_TAGS):]
1908
1909 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001910 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001911 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001912 if is_sha1 and not depth:
1913 # When syncing a specific commit and --depth is not set:
1914 # * if upstream is explicitly specified and is not a sha1, fetch only
1915 # upstream as users expect only upstream to be fetch.
1916 # Note: The commit might not be in upstream in which case the sync
1917 # will fail.
1918 # * otherwise, fetch all branches to make sure we end up with the
1919 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001920 if self.upstream:
1921 current_branch_only = not ID_RE.match(self.upstream)
1922 else:
1923 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001924
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001925 if not name:
1926 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001927
1928 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001929 remote = self.GetRemote(name)
1930 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001931 ssh_proxy = True
1932
Shawn O. Pearce88443382010-10-08 10:02:09 +02001933 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001934 if alt_dir and 'objects' == os.path.basename(alt_dir):
1935 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001936 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1937 remote = self.GetRemote(name)
1938
David Pursehouse8a68ff92012-09-24 12:15:13 +09001939 all_refs = self.bare_ref.all
1940 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001941 tmp = set()
1942
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301943 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001944 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001945 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001946 all_refs[r] = ref_id
1947 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001948 continue
1949
David Pursehouse8a68ff92012-09-24 12:15:13 +09001950 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001951 continue
1952
David Pursehouse8a68ff92012-09-24 12:15:13 +09001953 r = 'refs/_alt/%s' % ref_id
1954 all_refs[r] = ref_id
1955 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001956 tmp.add(r)
1957
Shawn O. Pearce88443382010-10-08 10:02:09 +02001958 tmp_packed = ''
1959 old_packed = ''
1960
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301961 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001962 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001963 tmp_packed += line
1964 if r not in tmp:
1965 old_packed += line
1966
1967 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001968 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001969 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001970
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001971 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001972
Conley Owensf97e8382015-01-21 11:12:46 -08001973 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001974 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001975 else:
1976 # If this repo has shallow objects, then we don't know which refs have
1977 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1978 # do this with projects that don't have shallow objects, since it is less
1979 # efficient.
1980 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1981 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001982
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001983 if quiet:
1984 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001985 if not self.worktree:
1986 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001987 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001988
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001989 # If using depth then we should not get all the tags since they may
1990 # be outside of the depth.
1991 if no_tags or depth:
1992 cmd.append('--no-tags')
1993 else:
1994 cmd.append('--tags')
1995
David Pursehouse74cfd272015-10-14 10:50:15 +09001996 if prune:
1997 cmd.append('--prune')
1998
Conley Owens80b87fe2014-05-09 17:13:44 -07001999 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002000 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002001 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002002 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002003 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002004 spec.append('tag')
2005 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002006
David Pursehouse403b64e2015-04-27 10:41:33 +09002007 if not self.manifest.IsMirror:
2008 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002009 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002010 # Shallow checkout of a specific commit, fetch from that commit and not
2011 # the heads only as the commit might be deeper in the history.
2012 spec.append(branch)
2013 else:
2014 if is_sha1:
2015 branch = self.upstream
2016 if branch is not None and branch.strip():
2017 if not branch.startswith('refs/'):
2018 branch = R_HEADS + branch
2019 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002020 cmd.extend(spec)
2021
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002022 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002023 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002024 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002025 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002026 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002027 ok = True
2028 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002029 # If needed, run the 'git remote prune' the first time through the loop
2030 elif (not _i and
2031 "error:" in gitcmd.stderr and
2032 "git remote prune" in gitcmd.stderr):
2033 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002034 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002035 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002036 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002037 break
2038 continue
Brian Harring14a66742012-09-28 20:21:57 -07002039 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002040 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2041 # in sha1 mode, we just tried sync'ing from the upstream field; it
2042 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002043 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002044 elif ret < 0:
2045 # Git died with a signal, exit immediately
2046 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002047 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002048
2049 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002050 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002051 if old_packed != '':
2052 _lwrite(packed_refs, old_packed)
2053 else:
2054 os.remove(packed_refs)
2055 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002056
2057 if is_sha1 and current_branch_only and self.upstream:
2058 # We just synced the upstream given branch; verify we
2059 # got what we wanted, else trigger a second run of all
2060 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002061 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002062 if not depth:
2063 # Avoid infinite recursion when depth is True (since depth implies
2064 # current_branch_only)
2065 return self._RemoteFetch(name=name, current_branch_only=False,
2066 initial=False, quiet=quiet, alt_dir=alt_dir)
2067 if self.clone_depth:
2068 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002069 return self._RemoteFetch(name=name,
2070 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002071 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002072
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002073 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002074
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002075 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002076 if initial and \
2077 (self.manifest.manifestProject.config.GetString('repo.depth') or
2078 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002079 return False
2080
2081 remote = self.GetRemote(self.remote.name)
2082 bundle_url = remote.url + '/clone.bundle'
2083 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002084 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2085 'persistent-http',
2086 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002087 return False
2088
2089 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2090 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2091
2092 exist_dst = os.path.exists(bundle_dst)
2093 exist_tmp = os.path.exists(bundle_tmp)
2094
2095 if not initial and not exist_dst and not exist_tmp:
2096 return False
2097
2098 if not exist_dst:
2099 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2100 if not exist_dst:
2101 return False
2102
2103 cmd = ['fetch']
2104 if quiet:
2105 cmd.append('--quiet')
2106 if not self.worktree:
2107 cmd.append('--update-head-ok')
2108 cmd.append(bundle_dst)
2109 for f in remote.fetch:
2110 cmd.append(str(f))
2111 cmd.append('refs/tags/*:refs/tags/*')
2112
2113 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002114 if os.path.exists(bundle_dst):
2115 os.remove(bundle_dst)
2116 if os.path.exists(bundle_tmp):
2117 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002118 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002119
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002120 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002121 if os.path.exists(dstPath):
2122 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002123
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002124 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002125 if quiet:
2126 cmd += ['--silent']
2127 if os.path.exists(tmpPath):
2128 size = os.stat(tmpPath).st_size
2129 if size >= 1024:
2130 cmd += ['--continue-at', '%d' % (size,)]
2131 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002132 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002133 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2134 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002135 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002136 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002137 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002138 if srcUrl.startswith('persistent-'):
2139 srcUrl = srcUrl[len('persistent-'):]
2140 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002141
Dave Borowitz137d0132015-01-02 11:12:54 -08002142 if IsTrace():
2143 Trace('%s', ' '.join(cmd))
2144 try:
2145 proc = subprocess.Popen(cmd)
2146 except OSError:
2147 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002148
Dave Borowitz137d0132015-01-02 11:12:54 -08002149 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002150
Dave Borowitz137d0132015-01-02 11:12:54 -08002151 if curlret == 22:
2152 # From curl man page:
2153 # 22: HTTP page not retrieved. The requested url was not found or
2154 # returned another error with the HTTP error code being 400 or above.
2155 # This return code only appears if -f, --fail is used.
2156 if not quiet:
2157 print("Server does not provide clone.bundle; ignoring.",
2158 file=sys.stderr)
2159 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002160
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002161 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002162 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002163 os.rename(tmpPath, dstPath)
2164 return True
2165 else:
2166 os.remove(tmpPath)
2167 return False
2168 else:
2169 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002170
Kris Giesingc8d882a2014-12-23 13:02:32 -08002171 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002172 try:
2173 with open(path) as f:
2174 if f.read(16) == '# v2 git bundle\n':
2175 return True
2176 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002177 if not quiet:
2178 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002179 return False
2180 except OSError:
2181 return False
2182
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002183 def _Checkout(self, rev, quiet=False):
2184 cmd = ['checkout']
2185 if quiet:
2186 cmd.append('-q')
2187 cmd.append(rev)
2188 cmd.append('--')
2189 if GitCommand(self, cmd).Wait() != 0:
2190 if self._allrefs:
2191 raise GitError('%s checkout %s ' % (self.name, rev))
2192
Anthony King7bdac712014-07-16 12:56:40 +01002193 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002194 cmd = ['cherry-pick']
2195 cmd.append(rev)
2196 cmd.append('--')
2197 if GitCommand(self, cmd).Wait() != 0:
2198 if self._allrefs:
2199 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2200
Anthony King7bdac712014-07-16 12:56:40 +01002201 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002202 cmd = ['revert']
2203 cmd.append('--no-edit')
2204 cmd.append(rev)
2205 cmd.append('--')
2206 if GitCommand(self, cmd).Wait() != 0:
2207 if self._allrefs:
2208 raise GitError('%s revert %s ' % (self.name, rev))
2209
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002210 def _ResetHard(self, rev, quiet=True):
2211 cmd = ['reset', '--hard']
2212 if quiet:
2213 cmd.append('-q')
2214 cmd.append(rev)
2215 if GitCommand(self, cmd).Wait() != 0:
2216 raise GitError('%s reset --hard %s ' % (self.name, rev))
2217
Anthony King7bdac712014-07-16 12:56:40 +01002218 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002219 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002220 if onto is not None:
2221 cmd.extend(['--onto', onto])
2222 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002223 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002224 raise GitError('%s rebase %s ' % (self.name, upstream))
2225
Pierre Tardy3d125942012-05-04 12:18:12 +02002226 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002227 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002228 if ffonly:
2229 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002230 if GitCommand(self, cmd).Wait() != 0:
2231 raise GitError('%s merge %s ' % (self.name, head))
2232
Kevin Degiabaa7f32014-11-12 11:27:45 -07002233 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002234 init_git_dir = not os.path.exists(self.gitdir)
2235 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002236 try:
2237 # Initialize the bare repository, which contains all of the objects.
2238 if init_obj_dir:
2239 os.makedirs(self.objdir)
2240 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002241
Kevin Degib1a07b82015-07-27 13:33:43 -06002242 # If we have a separate directory to hold refs, initialize it as well.
2243 if self.objdir != self.gitdir:
2244 if init_git_dir:
2245 os.makedirs(self.gitdir)
2246
2247 if init_obj_dir or init_git_dir:
2248 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2249 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002250 try:
2251 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2252 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002253 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002254 print("Retrying clone after deleting %s" %
2255 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002256 try:
2257 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002258 if self.worktree and os.path.exists(os.path.realpath
2259 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002260 shutil.rmtree(os.path.realpath(self.worktree))
2261 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2262 except:
2263 raise e
2264 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002265
Kevin Degi384b3c52014-10-16 16:02:58 -06002266 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002267 mp = self.manifest.manifestProject
2268 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002269
Kevin Degib1a07b82015-07-27 13:33:43 -06002270 if ref_dir or mirror_git:
2271 if not mirror_git:
2272 mirror_git = os.path.join(ref_dir, self.name + '.git')
2273 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2274 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002275
Kevin Degib1a07b82015-07-27 13:33:43 -06002276 if os.path.exists(mirror_git):
2277 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002278
Kevin Degib1a07b82015-07-27 13:33:43 -06002279 elif os.path.exists(repo_git):
2280 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002281
Kevin Degib1a07b82015-07-27 13:33:43 -06002282 else:
2283 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002284
Kevin Degib1a07b82015-07-27 13:33:43 -06002285 if ref_dir:
2286 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2287 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002288
Kevin Degib1a07b82015-07-27 13:33:43 -06002289 self._UpdateHooks()
2290
2291 m = self.manifest.manifestProject.config
2292 for key in ['user.name', 'user.email']:
2293 if m.Has(key, include_defaults=False):
2294 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002295 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002296 if self.manifest.IsMirror:
2297 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002298 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002299 self.config.SetString('core.bare', None)
2300 except Exception:
2301 if init_obj_dir and os.path.exists(self.objdir):
2302 shutil.rmtree(self.objdir)
2303 if init_git_dir and os.path.exists(self.gitdir):
2304 shutil.rmtree(self.gitdir)
2305 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002306
Jimmie Westera0444582012-10-24 13:44:42 +02002307 def _UpdateHooks(self):
2308 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002309 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002311 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002312 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002313 if not os.path.exists(hooks):
2314 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002315 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002316 name = os.path.basename(stock_hook)
2317
Victor Boivie65e0f352011-04-18 11:23:29 +02002318 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002319 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002320 # Don't install a Gerrit Code Review hook if this
2321 # project does not appear to use it for reviews.
2322 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002323 # Since the manifest project is one of those, but also
2324 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002325 continue
2326
2327 dst = os.path.join(hooks, name)
2328 if os.path.islink(dst):
2329 continue
2330 if os.path.exists(dst):
2331 if filecmp.cmp(stock_hook, dst, shallow=False):
2332 os.remove(dst)
2333 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002334 _warn("%s: Not replacing locally modified %s hook",
2335 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002336 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002337 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002338 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002339 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002340 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002341 raise GitError('filesystem must support symlinks')
2342 else:
2343 raise
2344
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002345 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002346 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002347 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002348 remote.url = self.remote.url
2349 remote.review = self.remote.review
2350 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002351
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002352 if self.worktree:
2353 remote.ResetFetch(mirror=False)
2354 else:
2355 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002356 remote.Save()
2357
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358 def _InitMRef(self):
2359 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002360 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002362 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002363 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002364
2365 def _InitAnyMRef(self, ref):
2366 cur = self.bare_ref.symref(ref)
2367
2368 if self.revisionId:
2369 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2370 msg = 'manifest set to %s' % self.revisionId
2371 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002372 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002373 else:
2374 remote = self.GetRemote(self.remote.name)
2375 dst = remote.ToLocal(self.revisionExpr)
2376 if cur != dst:
2377 msg = 'manifest set to %s' % self.revisionExpr
2378 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002379
Kevin Degi384b3c52014-10-16 16:02:58 -06002380 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002381 symlink_files = self.shareable_files[:]
2382 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002383 if share_refs:
2384 symlink_files += self.working_tree_files
2385 symlink_dirs += self.working_tree_dirs
2386 to_symlink = symlink_files + symlink_dirs
2387 for name in set(to_symlink):
2388 dst = os.path.realpath(os.path.join(destdir, name))
2389 if os.path.lexists(dst):
2390 src = os.path.realpath(os.path.join(srcdir, name))
2391 # Fail if the links are pointing to the wrong place
2392 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002393 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002394 'work tree. If you\'re comfortable with the '
2395 'possibility of losing the work tree\'s git metadata,'
2396 ' use `repo sync --force-sync {0}` to '
2397 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002398
David James8d201162013-10-11 17:03:19 -07002399 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2400 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2401
2402 Args:
2403 gitdir: The bare git repository. Must already be initialized.
2404 dotgit: The repository you would like to initialize.
2405 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2406 Only one work tree can store refs under a given |gitdir|.
2407 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2408 This saves you the effort of initializing |dotgit| yourself.
2409 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002410 symlink_files = self.shareable_files[:]
2411 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002412 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002413 symlink_files += self.working_tree_files
2414 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002415 to_symlink = symlink_files + symlink_dirs
2416
2417 to_copy = []
2418 if copy_all:
2419 to_copy = os.listdir(gitdir)
2420
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002421 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002422 for name in set(to_copy).union(to_symlink):
2423 try:
2424 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002425 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002426
Kevin Degi384b3c52014-10-16 16:02:58 -06002427 if os.path.lexists(dst):
2428 continue
David James8d201162013-10-11 17:03:19 -07002429
2430 # If the source dir doesn't exist, create an empty dir.
2431 if name in symlink_dirs and not os.path.lexists(src):
2432 os.makedirs(src)
2433
Conley Owens80b87fe2014-05-09 17:13:44 -07002434 # If the source file doesn't exist, ensure the destination
2435 # file doesn't either.
2436 if name in symlink_files and not os.path.lexists(src):
2437 try:
2438 os.remove(dst)
2439 except OSError:
2440 pass
2441
David James8d201162013-10-11 17:03:19 -07002442 if name in to_symlink:
2443 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2444 elif copy_all and not os.path.islink(dst):
2445 if os.path.isdir(src):
2446 shutil.copytree(src, dst)
2447 elif os.path.isfile(src):
2448 shutil.copy(src, dst)
2449 except OSError as e:
2450 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002451 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002452 else:
2453 raise
2454
Kevin Degiabaa7f32014-11-12 11:27:45 -07002455 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002457 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002458 try:
2459 if init_dotgit:
2460 os.makedirs(dotgit)
2461 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2462 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002463
Kevin Degiabaa7f32014-11-12 11:27:45 -07002464 try:
2465 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2466 except GitError as e:
2467 if force_sync:
2468 try:
2469 shutil.rmtree(dotgit)
2470 return self._InitWorkTree(force_sync=False)
2471 except:
2472 raise e
2473 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002474
Kevin Degib1a07b82015-07-27 13:33:43 -06002475 if init_dotgit:
2476 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477
Kevin Degib1a07b82015-07-27 13:33:43 -06002478 cmd = ['read-tree', '--reset', '-u']
2479 cmd.append('-v')
2480 cmd.append(HEAD)
2481 if GitCommand(self, cmd).Wait() != 0:
2482 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002483
Kevin Degib1a07b82015-07-27 13:33:43 -06002484 self._CopyAndLinkFiles()
2485 except Exception:
2486 if init_dotgit:
2487 shutil.rmtree(dotgit)
2488 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002489
2490 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002491 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002492
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002493 def _revlist(self, *args, **kw):
2494 a = []
2495 a.extend(args)
2496 a.append('--')
2497 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002498
2499 @property
2500 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002501 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002503 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002504 """Get logs between two revisions of this project."""
2505 comp = '..'
2506 if rev1:
2507 revs = [rev1]
2508 if rev2:
2509 revs.extend([comp, rev2])
2510 cmd = ['log', ''.join(revs)]
2511 out = DiffColoring(self.config)
2512 if out.is_on and color:
2513 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002514 if pretty_format is not None:
2515 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002516 if oneline:
2517 cmd.append('--oneline')
2518
2519 try:
2520 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2521 if log.Wait() == 0:
2522 return log.stdout
2523 except GitError:
2524 # worktree may not exist if groups changed for example. In that case,
2525 # try in gitdir instead.
2526 if not os.path.exists(self.worktree):
2527 return self.bare_git.log(*cmd[1:])
2528 else:
2529 raise
2530 return None
2531
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002532 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2533 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002534 """Get the list of logs from this revision to given revisionId"""
2535 logs = {}
2536 selfId = self.GetRevisionId(self._allrefs)
2537 toId = toProject.GetRevisionId(toProject._allrefs)
2538
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002539 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2540 pretty_format=pretty_format)
2541 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2542 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002543 return logs
2544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002545 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002546
David James8d201162013-10-11 17:03:19 -07002547 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002548 self._project = project
2549 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002550 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552 def LsOthers(self):
2553 p = GitCommand(self._project,
2554 ['ls-files',
2555 '-z',
2556 '--others',
2557 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002558 bare=False,
David James8d201162013-10-11 17:03:19 -07002559 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002560 capture_stdout=True,
2561 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002562 if p.Wait() == 0:
2563 out = p.stdout
2564 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002565 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002566 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002567 return []
2568
2569 def DiffZ(self, name, *args):
2570 cmd = [name]
2571 cmd.append('-z')
2572 cmd.extend(args)
2573 p = GitCommand(self._project,
2574 cmd,
David James8d201162013-10-11 17:03:19 -07002575 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002576 bare=False,
2577 capture_stdout=True,
2578 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002579 try:
2580 out = p.process.stdout.read()
2581 r = {}
2582 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002583 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002584 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002585 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002586 info = next(out)
2587 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002588 except StopIteration:
2589 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002590
2591 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002592
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002593 def __init__(self, path, omode, nmode, oid, nid, state):
2594 self.path = path
2595 self.src_path = None
2596 self.old_mode = omode
2597 self.new_mode = nmode
2598 self.old_id = oid
2599 self.new_id = nid
2600
2601 if len(state) == 1:
2602 self.status = state
2603 self.level = None
2604 else:
2605 self.status = state[:1]
2606 self.level = state[1:]
2607 while self.level.startswith('0'):
2608 self.level = self.level[1:]
2609
2610 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002611 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 if info.status in ('R', 'C'):
2613 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002614 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615 r[info.path] = info
2616 return r
2617 finally:
2618 p.Wait()
2619
2620 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002621 if self._bare:
2622 path = os.path.join(self._project.gitdir, HEAD)
2623 else:
2624 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002625 try:
2626 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002627 except IOError as e:
2628 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002629 try:
2630 line = fd.read()
2631 finally:
2632 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302633 try:
2634 line = line.decode()
2635 except AttributeError:
2636 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002637 if line.startswith('ref: '):
2638 return line[5:-1]
2639 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002640
2641 def SetHead(self, ref, message=None):
2642 cmdv = []
2643 if message is not None:
2644 cmdv.extend(['-m', message])
2645 cmdv.append(HEAD)
2646 cmdv.append(ref)
2647 self.symbolic_ref(*cmdv)
2648
2649 def DetachHead(self, new, message=None):
2650 cmdv = ['--no-deref']
2651 if message is not None:
2652 cmdv.extend(['-m', message])
2653 cmdv.append(HEAD)
2654 cmdv.append(new)
2655 self.update_ref(*cmdv)
2656
2657 def UpdateRef(self, name, new, old=None,
2658 message=None,
2659 detach=False):
2660 cmdv = []
2661 if message is not None:
2662 cmdv.extend(['-m', message])
2663 if detach:
2664 cmdv.append('--no-deref')
2665 cmdv.append(name)
2666 cmdv.append(new)
2667 if old is not None:
2668 cmdv.append(old)
2669 self.update_ref(*cmdv)
2670
2671 def DeleteRef(self, name, old=None):
2672 if not old:
2673 old = self.rev_parse(name)
2674 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002675 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002676
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002677 def rev_list(self, *args, **kw):
2678 if 'format' in kw:
2679 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2680 else:
2681 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002682 cmdv.extend(args)
2683 p = GitCommand(self._project,
2684 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002685 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002686 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002687 capture_stdout=True,
2688 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689 r = []
2690 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002691 if line[-1] == '\n':
2692 line = line[:-1]
2693 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002694 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002695 raise GitError('%s rev-list %s: %s' %
2696 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002697 return r
2698
2699 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002700 """Allow arbitrary git commands using pythonic syntax.
2701
2702 This allows you to do things like:
2703 git_obj.rev_parse('HEAD')
2704
2705 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2706 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002707 Any other positional arguments will be passed to the git command, and the
2708 following keyword arguments are supported:
2709 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002710
2711 Args:
2712 name: The name of the git command to call. Any '_' characters will
2713 be replaced with '-'.
2714
2715 Returns:
2716 A callable object that will try to call git with the named command.
2717 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002718 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002719
Dave Borowitz091f8932012-10-23 17:01:04 -07002720 def runner(*args, **kwargs):
2721 cmdv = []
2722 config = kwargs.pop('config', None)
2723 for k in kwargs:
2724 raise TypeError('%s() got an unexpected keyword argument %r'
2725 % (name, k))
2726 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002727 if not git_require((1, 7, 2)):
2728 raise ValueError('cannot set config on command line for %s()'
2729 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302730 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002731 cmdv.append('-c')
2732 cmdv.append('%s=%s' % (k, v))
2733 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002734 cmdv.extend(args)
2735 p = GitCommand(self._project,
2736 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002737 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002738 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002739 capture_stdout=True,
2740 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002741 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002742 raise GitError('%s %s: %s' %
2743 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002744 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302745 try:
Conley Owensedd01512013-09-26 12:59:58 -07002746 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302747 except AttributeError:
2748 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002749 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2750 return r[:-1]
2751 return r
2752 return runner
2753
2754
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002755class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002756
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002757 def __str__(self):
2758 return 'prior sync failed; rebase still in progress'
2759
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002760
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002761class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002762
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002763 def __str__(self):
2764 return 'contains uncommitted changes'
2765
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002766
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002767class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002768
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002769 def __init__(self, project, text):
2770 self.project = project
2771 self.text = text
2772
2773 def Print(self, syncbuf):
2774 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2775 syncbuf.out.nl()
2776
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002777
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002778class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002779
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002780 def __init__(self, project, why):
2781 self.project = project
2782 self.why = why
2783
2784 def Print(self, syncbuf):
2785 syncbuf.out.fail('error: %s/: %s',
2786 self.project.relpath,
2787 str(self.why))
2788 syncbuf.out.nl()
2789
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002790
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002791class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002792
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002793 def __init__(self, project, action):
2794 self.project = project
2795 self.action = action
2796
2797 def Run(self, syncbuf):
2798 out = syncbuf.out
2799 out.project('project %s/', self.project.relpath)
2800 out.nl()
2801 try:
2802 self.action()
2803 out.nl()
2804 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002805 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002806 out.nl()
2807 return False
2808
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002809
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002810class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002811
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002812 def __init__(self, config):
2813 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002814 self.project = self.printer('header', attr='bold')
2815 self.info = self.printer('info')
2816 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002817
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002818
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002819class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002820
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002821 def __init__(self, config, detach_head=False):
2822 self._messages = []
2823 self._failures = []
2824 self._later_queue1 = []
2825 self._later_queue2 = []
2826
2827 self.out = _SyncColoring(config)
2828 self.out.redirect(sys.stderr)
2829
2830 self.detach_head = detach_head
2831 self.clean = True
2832
2833 def info(self, project, fmt, *args):
2834 self._messages.append(_InfoMessage(project, fmt % args))
2835
2836 def fail(self, project, err=None):
2837 self._failures.append(_Failure(project, err))
2838 self.clean = False
2839
2840 def later1(self, project, what):
2841 self._later_queue1.append(_Later(project, what))
2842
2843 def later2(self, project, what):
2844 self._later_queue2.append(_Later(project, what))
2845
2846 def Finish(self):
2847 self._PrintMessages()
2848 self._RunLater()
2849 self._PrintMessages()
2850 return self.clean
2851
2852 def _RunLater(self):
2853 for q in ['_later_queue1', '_later_queue2']:
2854 if not self._RunQueue(q):
2855 return
2856
2857 def _RunQueue(self, queue):
2858 for m in getattr(self, queue):
2859 if not m.Run(self):
2860 self.clean = False
2861 return False
2862 setattr(self, queue, [])
2863 return True
2864
2865 def _PrintMessages(self):
2866 for m in self._messages:
2867 m.Print(self)
2868 for m in self._failures:
2869 m.Print(self)
2870
2871 self._messages = []
2872 self._failures = []
2873
2874
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002875class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002876
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002877 """A special project housed under .repo.
2878 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002879
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002880 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002882 manifest=manifest,
2883 name=name,
2884 gitdir=gitdir,
2885 objdir=gitdir,
2886 worktree=worktree,
2887 remote=RemoteSpec('origin'),
2888 relpath='.repo/%s' % name,
2889 revisionExpr='refs/heads/master',
2890 revisionId=None,
2891 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002892
2893 def PreSync(self):
2894 if self.Exists:
2895 cb = self.CurrentBranch
2896 if cb:
2897 base = self.GetBranch(cb).merge
2898 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002899 self.revisionExpr = base
2900 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002901
Anthony King7bdac712014-07-16 12:56:40 +01002902 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002903 """ Prepare MetaProject for manifest branch switch
2904 """
2905
2906 # detach and delete manifest branch, allowing a new
2907 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002908 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002909 self.Sync_LocalHalf(syncbuf)
2910 syncbuf.Finish()
2911
2912 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002913 ['update-ref', '-d', 'refs/heads/default'],
2914 capture_stdout=True,
2915 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002916
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002917 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002918 def LastFetch(self):
2919 try:
2920 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2921 return os.path.getmtime(fh)
2922 except OSError:
2923 return 0
2924
2925 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002926 def HasChanges(self):
2927 """Has the remote received new commits not yet checked out?
2928 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002929 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002930 return False
2931
David Pursehouse8a68ff92012-09-24 12:15:13 +09002932 all_refs = self.bare_ref.all
2933 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002934 head = self.work_git.GetHead()
2935 if head.startswith(R_HEADS):
2936 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002937 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002938 except KeyError:
2939 head = None
2940
2941 if revid == head:
2942 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002943 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002944 return True
2945 return False