blob: 54799858cde3a424f7db4c8d74b79b6d87d2b9dc [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040043if is_python3():
44 import urllib.parse
45else:
46 import imp
47 import urlparse
48 urllib = imp.new_module('urllib')
49 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090050 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053051 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090052 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053053
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070054
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055def _lwrite(path, content):
56 lock = '%s.lock' % path
57
Chirayu Desai303a82f2014-08-19 22:57:17 +053058 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 try:
60 fd.write(content)
61 finally:
62 fd.close()
63
64 try:
65 os.rename(lock, path)
66 except OSError:
67 os.remove(lock)
68 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
Jonathan Nieder93719792015-03-17 11:29:58 -070088_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070089
90
Jonathan Nieder93719792015-03-17 11:29:58 -070091def _ProjectHooks():
92 """List the hooks present in the 'hooks' directory.
93
94 These hooks are project hooks and are copied to the '.git/hooks' directory
95 of all subprojects.
96
97 This function caches the list of hooks (based on the contents of the
98 'repo/hooks' directory) on the first call.
99
100 Returns:
101 A list of absolute paths to all of the files in the hooks directory.
102 """
103 global _project_hook_list
104 if _project_hook_list is None:
105 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
106 d = os.path.join(d, 'hooks')
107 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
108 return _project_hook_list
109
110
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700111class DownloadedChange(object):
112 _commit_cache = None
113
114 def __init__(self, project, base, change_id, ps_id, commit):
115 self.project = project
116 self.base = base
117 self.change_id = change_id
118 self.ps_id = ps_id
119 self.commit = commit
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700124 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700150 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
151 '--abbrev-commit',
152 '--pretty=oneline',
153 '--reverse',
154 '--date-order',
155 not_rev(self.base),
156 R_HEADS + self.name,
157 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700176 def UploadForReview(self, people,
177 auto_topic=False,
178 draft=False,
179 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
Brian Harring435370c2012-07-28 15:37:04 -0700182 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400183 draft=draft,
184 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700186 def GetPublishedRefs(self):
187 refs = {}
188 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700189 self.branch.remote.SshReviewUrl(self.project.UserEmail),
190 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700191 for line in output.split('\n'):
192 try:
193 (sha, ref) = line.split()
194 refs[sha] = ref
195 except ValueError:
196 pass
197
198 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 def __init__(self, config):
204 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100205 self.project = self.printer('header', attr='bold')
206 self.branch = self.printer('header', attr='bold')
207 self.nobranch = self.printer('nobranch', fg='red')
208 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
Anthony King7bdac712014-07-16 12:56:40 +0100210 self.added = self.printer('added', fg='green')
211 self.changed = self.printer('changed', fg='red')
212 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
214
215class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700216
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 def __init__(self, config):
218 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100219 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221
Anthony King7bdac712014-07-16 12:56:40 +0100222class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
James W. Mills24c13082012-04-12 15:04:13 -0500224 def __init__(self, name, value, keep):
225 self.name = name
226 self.value = value
227 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229
Anthony King7bdac712014-07-16 12:56:40 +0100230class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800232 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233 self.src = src
234 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800235 self.abs_src = abssrc
236 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
238 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800239 src = self.abs_src
240 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 # copy file if it does not exist or is out of date
242 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
243 try:
244 # remove existing file first, since it might be read-only
245 if os.path.exists(dest):
246 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400247 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200248 dest_dir = os.path.dirname(dest)
249 if not os.path.isdir(dest_dir):
250 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 shutil.copy(src, dest)
252 # make the file read-only
253 mode = os.stat(dest)[stat.ST_MODE]
254 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
255 os.chmod(dest, mode)
256 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700257 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700259
Anthony King7bdac712014-07-16 12:56:40 +0100260class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Wink Saville4c426ef2015-06-03 08:05:17 -0700262 def __init__(self, git_worktree, src, dest, relsrc, absdest):
263 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500264 self.src = src
265 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700266 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500267 self.abs_dest = absdest
268
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500270 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700271 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 try:
273 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800274 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700275 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700277 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500278 if not os.path.isdir(dest_dir):
279 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700280 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700282 _error('Cannot link file %s to %s', relSrc, absDest)
283
284 def _Link(self):
285 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
286 on the src linking all of the files in the source in to the destination
287 directory.
288 """
289 # We use the absSrc to handle the situation where the current directory
290 # is not the root of the repo
291 absSrc = os.path.join(self.git_worktree, self.src)
292 if os.path.exists(absSrc):
293 # Entity exists so just a simple one to one link operation
294 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
295 else:
296 # Entity doesn't exist assume there is a wild card
297 absDestDir = self.abs_dest
298 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
299 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700300 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700301 else:
302 absSrcFiles = glob.glob(absSrc)
303 for absSrcFile in absSrcFiles:
304 # Create a releative path from source dir to destination dir
305 absSrcDir = os.path.dirname(absSrcFile)
306 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
307
308 # Get the source file name
309 srcFile = os.path.basename(absSrcFile)
310
311 # Now form the final full paths to srcFile. They will be
312 # absolute for the desintaiton and relative for the srouce.
313 absDest = os.path.join(absDestDir, srcFile)
314 relSrc = os.path.join(relSrcDir, srcFile)
315 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500316
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700317
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700318class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700319
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700320 def __init__(self,
321 name,
Anthony King7bdac712014-07-16 12:56:40 +0100322 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700323 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100324 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700325 revision=None,
326 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700327 self.name = name
328 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700329 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700330 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100331 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700332 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700334
Doug Anderson37282b42011-03-04 11:54:18 -0800335class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700336
Doug Anderson37282b42011-03-04 11:54:18 -0800337 """A RepoHook contains information about a script to run as a hook.
338
339 Hooks are used to run a python script before running an upload (for instance,
340 to run presubmit checks). Eventually, we may have hooks for other actions.
341
342 This shouldn't be confused with files in the 'repo/hooks' directory. Those
343 files are copied into each '.git/hooks' folder for each project. Repo-level
344 hooks are associated instead with repo actions.
345
346 Hooks are always python. When a hook is run, we will load the hook into the
347 interpreter and execute its main() function.
348 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Doug Anderson37282b42011-03-04 11:54:18 -0800350 def __init__(self,
351 hook_type,
352 hooks_project,
353 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400354 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800355 abort_if_user_denies=False):
356 """RepoHook constructor.
357
358 Params:
359 hook_type: A string representing the type of hook. This is also used
360 to figure out the name of the file containing the hook. For
361 example: 'pre-upload'.
362 hooks_project: The project containing the repo hooks. If you have a
363 manifest, this is manifest.repo_hooks_project. OK if this is None,
364 which will make the hook a no-op.
365 topdir: Repo's top directory (the one containing the .repo directory).
366 Scripts will run with CWD as this directory. If you have a manifest,
367 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400368 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800369 abort_if_user_denies: If True, we'll throw a HookError() if the user
370 doesn't allow us to run the hook.
371 """
372 self._hook_type = hook_type
373 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400374 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800375 self._topdir = topdir
376 self._abort_if_user_denies = abort_if_user_denies
377
378 # Store the full path to the script for convenience.
379 if self._hooks_project:
380 self._script_fullpath = os.path.join(self._hooks_project.worktree,
381 self._hook_type + '.py')
382 else:
383 self._script_fullpath = None
384
385 def _GetHash(self):
386 """Return a hash of the contents of the hooks directory.
387
388 We'll just use git to do this. This hash has the property that if anything
389 changes in the directory we will return a different has.
390
391 SECURITY CONSIDERATION:
392 This hash only represents the contents of files in the hook directory, not
393 any other files imported or called by hooks. Changes to imported files
394 can change the script behavior without affecting the hash.
395
396 Returns:
397 A string representing the hash. This will always be ASCII so that it can
398 be printed to the user easily.
399 """
400 assert self._hooks_project, "Must have hooks to calculate their hash."
401
402 # We will use the work_git object rather than just calling GetRevisionId().
403 # That gives us a hash of the latest checked in version of the files that
404 # the user will actually be executing. Specifically, GetRevisionId()
405 # doesn't appear to change even if a user checks out a different version
406 # of the hooks repo (via git checkout) nor if a user commits their own revs.
407 #
408 # NOTE: Local (non-committed) changes will not be factored into this hash.
409 # I think this is OK, since we're really only worried about warning the user
410 # about upstream changes.
411 return self._hooks_project.work_git.rev_parse('HEAD')
412
413 def _GetMustVerb(self):
414 """Return 'must' if the hook is required; 'should' if not."""
415 if self._abort_if_user_denies:
416 return 'must'
417 else:
418 return 'should'
419
420 def _CheckForHookApproval(self):
421 """Check to see whether this hook has been approved.
422
Mike Frysinger40252c22016-08-15 21:23:44 -0400423 We'll accept approval of manifest URLs if they're using secure transports.
424 This way the user can say they trust the manifest hoster. For insecure
425 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800426
427 Note that we ask permission for each individual hook even though we use
428 the hash of all hooks when detecting changes. We'd like the user to be
429 able to approve / deny each hook individually. We only use the hash of all
430 hooks because there is no other easy way to detect changes to local imports.
431
432 Returns:
433 True if this hook is approved to run; False otherwise.
434
435 Raises:
436 HookError: Raised if the user doesn't approve and abort_if_user_denies
437 was passed to the consturctor.
438 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400439 if self._ManifestUrlHasSecureScheme():
440 return self._CheckForHookApprovalManifest()
441 else:
442 return self._CheckForHookApprovalHash()
443
444 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
445 changed_prompt):
446 """Check for approval for a particular attribute and hook.
Doug Anderson37282b42011-03-04 11:54:18 -0800447
Mike Frysinger40252c22016-08-15 21:23:44 -0400448 Args:
449 subkey: The git config key under [repo.hooks.<hook_type>] to store the
450 last approved string.
451 new_val: The new value to compare against the last approved one.
452 main_prompt: Message to display to the user to ask for approval.
453 changed_prompt: Message explaining why we're re-asking for approval.
Doug Anderson37282b42011-03-04 11:54:18 -0800454
Mike Frysinger40252c22016-08-15 21:23:44 -0400455 Returns:
456 True if this hook is approved to run; False otherwise.
Doug Anderson37282b42011-03-04 11:54:18 -0800457
Mike Frysinger40252c22016-08-15 21:23:44 -0400458 Raises:
459 HookError: Raised if the user doesn't approve and abort_if_user_denies
460 was passed to the consturctor.
461 """
462 hooks_config = self._hooks_project.config
463 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800464
Mike Frysinger40252c22016-08-15 21:23:44 -0400465 # Get the last value that the user approved for this hook; may be None.
466 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800467
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800469 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800471 # Approval matched. We're done.
472 return True
473 else:
474 # Give the user a reason why we're prompting, since they last told
475 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400476 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800477 else:
478 prompt = ''
479
480 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
481 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400482 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530483 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900484 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800485
486 # User is doing a one-time approval.
487 if response in ('y', 'yes'):
488 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400489 elif response == 'always':
490 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800491 return True
492
493 # For anything else, we'll assume no approval.
494 if self._abort_if_user_denies:
495 raise HookError('You must allow the %s hook or use --no-verify.' %
496 self._hook_type)
497
498 return False
499
Mike Frysinger40252c22016-08-15 21:23:44 -0400500 def _ManifestUrlHasSecureScheme(self):
501 """Check if the URI for the manifest is a secure transport."""
502 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
503 parse_results = urllib.parse.urlparse(self._manifest_url)
504 return parse_results.scheme in secure_schemes
505
506 def _CheckForHookApprovalManifest(self):
507 """Check whether the user has approved this manifest host.
508
509 Returns:
510 True if this hook is approved to run; False otherwise.
511 """
512 return self._CheckForHookApprovalHelper(
513 'approvedmanifest',
514 self._manifest_url,
515 'Run hook scripts from %s' % (self._manifest_url,),
516 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
517
518 def _CheckForHookApprovalHash(self):
519 """Check whether the user has approved the hooks repo.
520
521 Returns:
522 True if this hook is approved to run; False otherwise.
523 """
524 prompt = ('Repo %s run the script:\n'
525 ' %s\n'
526 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700527 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400528 return self._CheckForHookApprovalHelper(
529 'approvedhash',
530 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700531 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400532 'Scripts have changed since %s was allowed.' % (self._hook_type,))
533
Doug Anderson37282b42011-03-04 11:54:18 -0800534 def _ExecuteHook(self, **kwargs):
535 """Actually execute the given hook.
536
537 This will run the hook's 'main' function in our python interpreter.
538
539 Args:
540 kwargs: Keyword arguments to pass to the hook. These are often specific
541 to the hook type. For instance, pre-upload hooks will contain
542 a project_list.
543 """
544 # Keep sys.path and CWD stashed away so that we can always restore them
545 # upon function exit.
546 orig_path = os.getcwd()
547 orig_syspath = sys.path
548
549 try:
550 # Always run hooks with CWD as topdir.
551 os.chdir(self._topdir)
552
553 # Put the hook dir as the first item of sys.path so hooks can do
554 # relative imports. We want to replace the repo dir as [0] so
555 # hooks can't import repo files.
556 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
557
558 # Exec, storing global context in the context dict. We catch exceptions
559 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500560 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800561 try:
Anthony King70f68902014-05-05 21:15:34 +0100562 exec(compile(open(self._script_fullpath).read(),
563 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800564 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700565 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
566 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800567
568 # Running the script should have defined a main() function.
569 if 'main' not in context:
570 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
571
Doug Anderson37282b42011-03-04 11:54:18 -0800572 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
573 # We don't actually want hooks to define their main with this argument--
574 # it's there to remind them that their hook should always take **kwargs.
575 # For instance, a pre-upload hook should be defined like:
576 # def main(project_list, **kwargs):
577 #
578 # This allows us to later expand the API without breaking old hooks.
579 kwargs = kwargs.copy()
580 kwargs['hook_should_take_kwargs'] = True
581
582 # Call the main function in the hook. If the hook should cause the
583 # build to fail, it will raise an Exception. We'll catch that convert
584 # to a HookError w/ just the failing traceback.
585 try:
586 context['main'](**kwargs)
587 except Exception:
588 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700589 'above.' % (traceback.format_exc(),
590 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800591 finally:
592 # Restore sys.path and CWD.
593 sys.path = orig_syspath
594 os.chdir(orig_path)
595
596 def Run(self, user_allows_all_hooks, **kwargs):
597 """Run the hook.
598
599 If the hook doesn't exist (because there is no hooks project or because
600 this particular hook is not enabled), this is a no-op.
601
602 Args:
603 user_allows_all_hooks: If True, we will never prompt about running the
604 hook--we'll just assume it's OK to run it.
605 kwargs: Keyword arguments to pass to the hook. These are often specific
606 to the hook type. For instance, pre-upload hooks will contain
607 a project_list.
608
609 Raises:
610 HookError: If there was a problem finding the hook or the user declined
611 to run a required hook (from _CheckForHookApproval).
612 """
613 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700614 if ((not self._hooks_project) or (self._hook_type not in
615 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800616 return
617
618 # Bail with a nice error if we can't find the hook.
619 if not os.path.isfile(self._script_fullpath):
620 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
621
622 # Make sure the user is OK with running the hook.
623 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
624 return
625
626 # Run the hook with the same version of python we're using.
627 self._ExecuteHook(**kwargs)
628
629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600631 # These objects can be shared between several working trees.
632 shareable_files = ['description', 'info']
natalie.chene8996f92015-12-29 10:53:30 +0800633 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn', 'lfs']
Kevin Degi384b3c52014-10-16 16:02:58 -0600634 # These objects can only be used by a single working tree.
635 working_tree_files = ['config', 'packed-refs', 'shallow']
636 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 def __init__(self,
639 manifest,
640 name,
641 remote,
642 gitdir,
David James8d201162013-10-11 17:03:19 -0700643 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644 worktree,
645 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700646 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800647 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100648 rebase=True,
649 groups=None,
650 sync_c=False,
651 sync_s=False,
652 clone_depth=None,
653 upstream=None,
654 parent=None,
655 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900656 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700657 optimized_fetch=False,
natalie.chene8996f92015-12-29 10:53:30 +0800658 old_revision=None,
659 lfs_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800660 """Init a Project object.
661
662 Args:
663 manifest: The XmlManifest object.
664 name: The `name` attribute of manifest.xml's project element.
665 remote: RemoteSpec object specifying its remote's properties.
666 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700667 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800668 worktree: Absolute path of git working tree.
669 relpath: Relative path of git working tree to repo's top directory.
670 revisionExpr: The `revision` attribute of manifest.xml's project element.
671 revisionId: git commit id for checking out.
672 rebase: The `rebase` attribute of manifest.xml's project element.
673 groups: The `groups` attribute of manifest.xml's project element.
674 sync_c: The `sync-c` attribute of manifest.xml's project element.
675 sync_s: The `sync-s` attribute of manifest.xml's project element.
676 upstream: The `upstream` attribute of manifest.xml's project element.
677 parent: The parent Project object.
678 is_derived: False if the project was explicitly defined in the manifest;
679 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400680 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900681 optimized_fetch: If True, when a project is set to a sha1 revision, only
682 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700683 old_revision: saved git commit id for open GITC projects.
natalie.chene8996f92015-12-29 10:53:30 +0800684 lfs_fetch: git lfs fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800685 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686 self.manifest = manifest
687 self.name = name
688 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800689 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700690 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800691 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700692 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800693 else:
694 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700696 self.revisionExpr = revisionExpr
697
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700698 if revisionId is None \
699 and revisionExpr \
700 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700701 self.revisionId = revisionExpr
702 else:
703 self.revisionId = revisionId
704
Mike Pontillod3153822012-02-28 11:53:24 -0800705 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700706 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700707 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800708 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900709 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700710 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800711 self.parent = parent
712 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900713 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800714 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800715
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500718 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500719 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700720 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
721 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800723 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700724 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800725 else:
726 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700727 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700728 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700729 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400730 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700731 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
Doug Anderson37282b42011-03-04 11:54:18 -0800733 # This will be filled in if a project is later identified to be the
734 # project containing repo hooks.
735 self.enabled_repo_hooks = []
natalie.chene8996f92015-12-29 10:53:30 +0800736 self.lfs_fetch = lfs_fetch
Doug Anderson37282b42011-03-04 11:54:18 -0800737
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800739 def Derived(self):
740 return self.is_derived
741
742 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600744 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745
746 @property
747 def CurrentBranch(self):
748 """Obtain the name of the currently checked out branch.
749 The branch name omits the 'refs/heads/' prefix.
750 None is returned if the project is on a detached HEAD.
751 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700752 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753 if b.startswith(R_HEADS):
754 return b[len(R_HEADS):]
755 return None
756
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700757 def IsRebaseInProgress(self):
758 w = self.worktree
759 g = os.path.join(w, '.git')
760 return os.path.exists(os.path.join(g, 'rebase-apply')) \
761 or os.path.exists(os.path.join(g, 'rebase-merge')) \
762 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 def IsDirty(self, consider_untracked=True):
765 """Is the working directory modified in some way?
766 """
767 self.work_git.update_index('-q',
768 '--unmerged',
769 '--ignore-missing',
770 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900771 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772 return True
773 if self.work_git.DiffZ('diff-files'):
774 return True
775 if consider_untracked and self.work_git.LsOthers():
776 return True
777 return False
778
779 _userident_name = None
780 _userident_email = None
781
782 @property
783 def UserName(self):
784 """Obtain the user's personal name.
785 """
786 if self._userident_name is None:
787 self._LoadUserIdentity()
788 return self._userident_name
789
790 @property
791 def UserEmail(self):
792 """Obtain the user's email address. This is very likely
793 to be their Gerrit login.
794 """
795 if self._userident_email is None:
796 self._LoadUserIdentity()
797 return self._userident_email
798
799 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900800 u = self.bare_git.var('GIT_COMMITTER_IDENT')
801 m = re.compile("^(.*) <([^>]*)> ").match(u)
802 if m:
803 self._userident_name = m.group(1)
804 self._userident_email = m.group(2)
805 else:
806 self._userident_name = ''
807 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808
809 def GetRemote(self, name):
810 """Get the configuration for a single remote.
811 """
812 return self.config.GetRemote(name)
813
814 def GetBranch(self, name):
815 """Get the configuration for a single branch.
816 """
817 return self.config.GetBranch(name)
818
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700819 def GetBranches(self):
820 """Get all existing local branches.
821 """
822 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900823 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700824 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700825
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530826 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700827 if name.startswith(R_HEADS):
828 name = name[len(R_HEADS):]
829 b = self.GetBranch(name)
830 b.current = name == current
831 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900832 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700833 heads[name] = b
834
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530835 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700836 if name.startswith(R_PUB):
837 name = name[len(R_PUB):]
838 b = heads.get(name)
839 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900840 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700841
842 return heads
843
Colin Cross5acde752012-03-28 20:15:45 -0700844 def MatchesGroups(self, manifest_groups):
845 """Returns true if the manifest groups specified at init should cause
846 this project to be synced.
847 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700848 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700849
Conley Owens971de8e2012-04-16 10:36:08 -0700850 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700851 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700852 manifest_groups: "-group1,group2"
853 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500854
855 The special manifest group "default" will match any project that
856 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700857 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500858 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700859 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700860 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500861 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700862
Conley Owens971de8e2012-04-16 10:36:08 -0700863 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700864 for group in expanded_manifest_groups:
865 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700866 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700867 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700868 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700869
Conley Owens971de8e2012-04-16 10:36:08 -0700870 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700872# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700873 def UncommitedFiles(self, get_all=True):
874 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700876 Args:
877 get_all: a boolean, if True - get information about all different
878 uncommitted files. If False - return as soon as any kind of
879 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500880 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700881 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500882 self.work_git.update_index('-q',
883 '--unmerged',
884 '--ignore-missing',
885 '--refresh')
886 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700887 details.append("rebase in progress")
888 if not get_all:
889 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500890
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700891 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
892 if changes:
893 details.extend(changes)
894 if not get_all:
895 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500896
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700897 changes = self.work_git.DiffZ('diff-files').keys()
898 if changes:
899 details.extend(changes)
900 if not get_all:
901 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500902
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700903 changes = self.work_git.LsOthers()
904 if changes:
905 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500906
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700907 return details
908
909 def HasChanges(self):
910 """Returns true if there are uncommitted changes.
911 """
912 if self.UncommitedFiles(get_all=False):
913 return True
914 else:
915 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500916
Terence Haddock4655e812011-03-31 12:33:34 +0200917 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200919
920 Args:
921 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """
923 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700924 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200925 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700926 print(file=output_redir)
927 print('project %s/' % self.relpath, file=output_redir)
928 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 return
930
931 self.work_git.update_index('-q',
932 '--unmerged',
933 '--ignore-missing',
934 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700935 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
937 df = self.work_git.DiffZ('diff-files')
938 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100939 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700940 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941
942 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700943 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200944 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700945 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946
947 branch = self.CurrentBranch
948 if branch is None:
949 out.nobranch('(*** NO BRANCH ***)')
950 else:
951 out.branch('branch %s', branch)
952 out.nl()
953
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700954 if rb:
955 out.important('prior sync failed; rebase still in progress')
956 out.nl()
957
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 paths = list()
959 paths.extend(di.keys())
960 paths.extend(df.keys())
961 paths.extend(do)
962
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530963 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900964 try:
965 i = di[p]
966 except KeyError:
967 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900969 try:
970 f = df[p]
971 except KeyError:
972 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200973
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900974 if i:
975 i_status = i.status.upper()
976 else:
977 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900979 if f:
980 f_status = f.status.lower()
981 else:
982 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983
984 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800985 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700986 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 else:
988 line = ' %s%s\t%s' % (i_status, f_status, p)
989
990 if i and not f:
991 out.added('%s', line)
992 elif (i and f) or (not i and f):
993 out.changed('%s', line)
994 elif not i and not f:
995 out.untracked('%s', line)
996 else:
997 out.write('%s', line)
998 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200999
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001000 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001
pelyad67872d2012-03-28 14:49:58 +03001002 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 """Prints the status of the repository to stdout.
1004 """
1005 out = DiffColoring(self.config)
1006 cmd = ['diff']
1007 if out.is_on:
1008 cmd.append('--color')
1009 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001010 if absolute_paths:
1011 cmd.append('--src-prefix=a/%s/' % self.relpath)
1012 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 cmd.append('--')
1014 p = GitCommand(self,
1015 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001016 capture_stdout=True,
1017 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 has_diff = False
1019 for line in p.process.stdout:
1020 if not has_diff:
1021 out.nl()
1022 out.project('project %s/' % self.relpath)
1023 out.nl()
1024 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001025 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026 p.Wait()
1027
1028
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001029# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030
David Pursehouse8a68ff92012-09-24 12:15:13 +09001031 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 """Was the branch published (uploaded) for code review?
1033 If so, returns the SHA-1 hash of the last published
1034 state for the branch.
1035 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001036 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001037 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001038 try:
1039 return self.bare_git.rev_parse(key)
1040 except GitError:
1041 return None
1042 else:
1043 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001044 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 except KeyError:
1046 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 """Prunes any stale published refs.
1050 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001051 if all_refs is None:
1052 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 heads = set()
1054 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301055 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 if name.startswith(R_HEADS):
1057 heads.add(name)
1058 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001059 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301061 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 n = name[len(R_PUB):]
1063 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001064 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001066 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 """List any branches which can be uploaded for review.
1068 """
1069 heads = {}
1070 pubed = {}
1071
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301072 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001074 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001076 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077
1078 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301079 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001080 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001082 if selected_branch and branch != selected_branch:
1083 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001085 rb = self.GetUploadableBranch(branch)
1086 if rb:
1087 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 return ready
1089
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001090 def GetUploadableBranch(self, branch_name):
1091 """Get a single uploadable branch, or None.
1092 """
1093 branch = self.GetBranch(branch_name)
1094 base = branch.LocalMerge
1095 if branch.LocalMerge:
1096 rb = ReviewableBranch(self, branch, base)
1097 if rb.commits:
1098 return rb
1099 return None
1100
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001101 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001102 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001103 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001104 draft=False,
1105 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106 """Uploads the named branch for code review.
1107 """
1108 if branch is None:
1109 branch = self.CurrentBranch
1110 if branch is None:
1111 raise GitError('not currently on a branch')
1112
1113 branch = self.GetBranch(branch)
1114 if not branch.LocalMerge:
1115 raise GitError('branch %s does not track a remote' % branch.name)
1116 if not branch.remote.review:
1117 raise GitError('remote %s has no review url' % branch.remote.name)
1118
Bryan Jacobsf609f912013-05-06 13:36:24 -04001119 if dest_branch is None:
1120 dest_branch = self.dest_branch
1121 if dest_branch is None:
1122 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 if not dest_branch.startswith(R_HEADS):
1124 dest_branch = R_HEADS + dest_branch
1125
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001126 if not branch.remote.projectname:
1127 branch.remote.projectname = self.name
1128 branch.remote.Save()
1129
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001130 url = branch.remote.ReviewUrl(self.UserEmail)
1131 if url is None:
1132 raise UploadError('review not configured')
1133 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001134
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001135 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001136 rp = ['gerrit receive-pack']
1137 for e in people[0]:
1138 rp.append('--reviewer=%s' % sq(e))
1139 for e in people[1]:
1140 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001141 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001142
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001143 cmd.append(url)
1144
1145 if dest_branch.startswith(R_HEADS):
1146 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001147
1148 upload_type = 'for'
1149 if draft:
1150 upload_type = 'drafts'
1151
1152 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1153 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001154 if auto_topic:
1155 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001156 if not url.startswith('ssh://'):
1157 rp = ['r=%s' % p for p in people[0]] + \
1158 ['cc=%s' % p for p in people[1]]
1159 if rp:
1160 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001161 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001162
Anthony King7bdac712014-07-16 12:56:40 +01001163 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001164 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165
1166 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1167 self.bare_git.UpdateRef(R_PUB + branch.name,
1168 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001169 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
1171
natalie.chene8996f92015-12-29 10:53:30 +08001172 def __FetchLfsObjects(self, name, refs):
1173 if 'refs/heads/*' in refs:
1174 refs = []
1175 for ref in GitRefs(self.objdir).all:
1176 if ref.startswith(R_HEADS):
1177 refs.append(ref)
1178 cmd = ['lfs', 'fetch']
1179 cmd.append(name)
1180 cmd.extend(refs)
1181 gitcmd = GitCommand(self, cmd, bare=True)
1182 ret = gitcmd.Wait()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183
natalie.chen9a61a912017-05-26 16:35:48 +08001184 def _CreateLocalMergeBranch(self, branch, revid):
1185 if not branch and not ID_RE.match(revid):
1186 return
1187 ref = os.path.join(self.gitdir, branch)
1188 if os.path.exists(ref): return
natalie.chenf453ba82017-04-21 16:46:39 +08001189 try:
natalie.chen9a61a912017-05-26 16:35:48 +08001190 self.work_git.rev_parse(branch)
natalie.chenf453ba82017-04-21 16:46:39 +08001191 except GitError:
natalie.chenf453ba82017-04-21 16:46:39 +08001192 try:
1193 os.makedirs(os.path.dirname(ref))
1194 except OSError:
1195 pass
1196 _lwrite(ref, '%s\n' % revid)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001197# Sync ##
natalie.chenf453ba82017-04-21 16:46:39 +08001198
Julien Campergue335f5ef2013-10-16 11:02:35 +02001199 def _ExtractArchive(self, tarpath, path=None):
1200 """Extract the given tar on its current location
1201
1202 Args:
1203 - tarpath: The path to the actual tar file
1204
1205 """
1206 try:
1207 with tarfile.open(tarpath, 'r') as tar:
1208 tar.extractall(path=path)
1209 return True
1210 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001211 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001212 return False
1213
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001214 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001215 quiet=False,
1216 is_new=None,
1217 current_branch_only=False,
1218 force_sync=False,
1219 clone_bundle=True,
1220 no_tags=False,
1221 archive=False,
1222 optimized_fetch=False,
1223 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 """Perform only the network IO portion of the sync process.
1225 Local working directory/branch state is not affected.
1226 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001227 if archive and not isinstance(self, MetaProject):
1228 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001229 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001230 return False
1231
1232 name = self.relpath.replace('\\', '/')
1233 name = name.replace('/', '_')
1234 tarpath = '%s.tar' % name
1235 topdir = self.manifest.topdir
1236
1237 try:
1238 self._FetchArchive(tarpath, cwd=topdir)
1239 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001240 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001241 return False
1242
1243 # From now on, we only need absolute tarpath
1244 tarpath = os.path.join(topdir, tarpath)
1245
1246 if not self._ExtractArchive(tarpath, path=topdir):
1247 return False
1248 try:
1249 os.remove(tarpath)
1250 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001251 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001252 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001253 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001254 if is_new is None:
1255 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001256 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001257 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001258 else:
1259 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001261
1262 if is_new:
1263 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1264 try:
1265 fd = open(alt, 'rb')
1266 try:
1267 alt_dir = fd.readline().rstrip()
1268 finally:
1269 fd.close()
1270 except IOError:
1271 alt_dir = None
1272 else:
1273 alt_dir = None
1274
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001275 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001276 and alt_dir is None \
1277 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001278 is_new = False
1279
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001280 if not current_branch_only:
1281 if self.sync_c:
1282 current_branch_only = True
1283 elif not self.manifest._loaded:
1284 # Manifest cannot check defaults until it syncs.
1285 current_branch_only = False
1286 elif self.manifest.default.sync_c:
1287 current_branch_only = True
1288
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001289 need_to_fetch = not (optimized_fetch and
1290 (ID_RE.match(self.revisionExpr) and
1291 self._CheckForSha1()))
1292 if (need_to_fetch and
1293 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1294 current_branch_only=current_branch_only,
1295 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001296 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001297
1298 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001299 self._InitMRef()
1300 else:
1301 self._InitMirrorHead()
1302 try:
1303 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1304 except OSError:
1305 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001307
1308 def PostRepoUpgrade(self):
1309 self._InitHooks()
1310
natalie.chendc3502d2017-05-19 17:01:37 +08001311 def RemoveOldCopyAndLinkFiles(self, path = None):
1312 old_copylink = []
1313 file_path = os.path.join(self.gitdir if not path else path, '.repo_copylink')
1314 if os.path.exists(file_path):
1315 fd = open(file_path, 'r')
1316 try:
1317 old_copylink = fd.read().split('\n')
1318 finally:
1319 fd.close()
natalie.chen1db7cb62017-08-16 09:23:19 +08001320 for dest in old_copylink:
1321 if not dest: continue
1322 target = os.path.join(self.manifest.topdir, dest)
natalie.chen19593222018-02-05 20:49:35 +08001323 if os.path.lexists(target):
natalie.chendc3502d2017-05-19 17:01:37 +08001324 if IsTrace():
1325 Trace('rm %s', target)
1326 os.remove(target)
1327
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001328 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001329 if self.manifest.isGitcClient:
1330 return
natalie.chendc3502d2017-05-19 17:01:37 +08001331 self.RemoveOldCopyAndLinkFiles()
1332 new_copylink = []
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 for copyfile in self.copyfiles:
1334 copyfile._Copy()
natalie.chen1db7cb62017-08-16 09:23:19 +08001335 new_copylink.append(copyfile.dest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001336 for linkfile in self.linkfiles:
1337 linkfile._Link()
natalie.chen1db7cb62017-08-16 09:23:19 +08001338 new_copylink.append(linkfile.dest)
natalie.chendc3502d2017-05-19 17:01:37 +08001339 file_path = os.path.join(self.gitdir, '.repo_copylink')
1340 fd = open(file_path, 'w')
1341 try:
1342 fd.write('\n'.join(new_copylink))
1343 fd.write('\n')
1344 finally:
1345 fd.close()
1346
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Julien Camperguedd654222014-01-09 16:21:37 +01001348 def GetCommitRevisionId(self):
1349 """Get revisionId of a commit.
1350
1351 Use this method instead of GetRevisionId to get the id of the commit rather
1352 than the id of the current git object (for example, a tag)
1353
1354 """
1355 if not self.revisionExpr.startswith(R_TAGS):
1356 return self.GetRevisionId(self._allrefs)
1357
1358 try:
1359 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1360 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001361 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1362 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001363
David Pursehouse8a68ff92012-09-24 12:15:13 +09001364 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001365 if self.revisionId:
1366 return self.revisionId
1367
1368 rem = self.GetRemote(self.remote.name)
1369 rev = rem.ToLocal(self.revisionExpr)
1370
David Pursehouse8a68ff92012-09-24 12:15:13 +09001371 if all_refs is not None and rev in all_refs:
1372 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001373
1374 try:
1375 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1376 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001377 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1378 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379
Kevin Degiabaa7f32014-11-12 11:27:45 -07001380 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 """Perform only the local IO portion of the sync process.
1382 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001384 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001385 all_refs = self.bare_ref.all
1386 self.CleanPublishedCache(all_refs)
1387 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001388
David Pursehouse1d947b32012-10-25 12:23:11 +09001389 def _doff():
1390 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001391 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001392
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001393 head = self.work_git.GetHead()
1394 if head.startswith(R_HEADS):
1395 branch = head[len(R_HEADS):]
1396 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001397 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001398 except KeyError:
1399 head = None
1400 else:
1401 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001403 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404 # Currently on a detached HEAD. The user is assumed to
1405 # not have any local modifications worth worrying about.
1406 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001407 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001408 syncbuf.fail(self, _PriorSyncFailedError())
1409 return
1410
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001411 if head == revid:
1412 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001413 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001414 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001415 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001416 # The copy/linkfile config may have changed.
1417 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001418 return
1419 else:
1420 lost = self._revlist(not_rev(revid), HEAD)
1421 if lost:
1422 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001423
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001425 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001426 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001427 syncbuf.fail(self, e)
1428 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001429 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001430 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001431
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001432 if head == revid:
1433 # No changes; don't do anything further.
1434 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001435 # The copy/linkfile config may have changed.
1436 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001437 return
1438
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001439 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001440
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001441 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001442 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001443 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001444 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001445 syncbuf.info(self,
1446 "leaving %s; does not track upstream",
1447 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001448 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001449 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001450 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001451 syncbuf.fail(self, e)
1452 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001453 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001454 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001455
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001456 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001457 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001459 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001460 if not_merged:
1461 if upstream_gain:
1462 # The user has published this branch and some of those
1463 # commits are not yet merged upstream. We do not want
1464 # to rewrite the published commits so we punt.
1465 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001466 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001467 "branch %s is published (but not merged) and is now "
1468 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001469 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001470 elif pub == head:
1471 # All published commits are merged, and thus we are a
1472 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001473 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001474 syncbuf.later1(self, _doff)
1475 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001476
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001477 # Examine the local commits not in the remote. Find the
1478 # last one attributed to this user, if any.
1479 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001480 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001481 last_mine = None
1482 cnt_mine = 0
1483 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301484 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001485 if committer_email == self.UserEmail:
1486 last_mine = commit_id
1487 cnt_mine += 1
1488
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001489 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001490 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491
1492 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001493 syncbuf.fail(self, _DirtyError())
1494 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001496 # If the upstream switched on us, warn the user.
1497 #
1498 if branch.merge != self.revisionExpr:
1499 if branch.merge and self.revisionExpr:
1500 syncbuf.info(self,
1501 'manifest switched %s...%s',
1502 branch.merge,
1503 self.revisionExpr)
1504 elif branch.merge:
1505 syncbuf.info(self,
1506 'manifest no longer tracks %s',
1507 branch.merge)
1508
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001509 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001510 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001511 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001513 syncbuf.info(self,
1514 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001515 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001516
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001517 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001518 if not ID_RE.match(self.revisionExpr):
1519 # in case of manifest sync the revisionExpr might be a SHA1
1520 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001521 if not branch.merge.startswith('refs/'):
1522 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523 branch.Save()
1524
Mike Pontillod3153822012-02-28 11:53:24 -08001525 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001526 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001527 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001528 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001529 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001530 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001531 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001532 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001533 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001534 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001535 syncbuf.fail(self, e)
1536 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001537 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001538 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001540 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001541 # dest should already be an absolute path, but src is project relative
1542 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001543 abssrc = os.path.join(self.worktree, src)
1544 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001545
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001546 def AddLinkFile(self, src, dest, absdest):
1547 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001548 # make src relative path to dest
1549 absdestdir = os.path.dirname(absdest)
1550 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001551 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001552
James W. Mills24c13082012-04-12 15:04:13 -05001553 def AddAnnotation(self, name, value, keep):
1554 self.annotations.append(_Annotation(name, value, keep))
1555
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001556 def DownloadPatchSet(self, change_id, patch_id):
1557 """Download a single patch set of a single change to FETCH_HEAD.
1558 """
1559 remote = self.GetRemote(self.remote.name)
1560
1561 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001562 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001563 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001564 if GitCommand(self, cmd, bare=True).Wait() != 0:
1565 return None
1566 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001567 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001568 change_id,
1569 patch_id,
1570 self.bare_git.rev_parse('FETCH_HEAD'))
1571
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001573# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574
Simran Basib9a1b732015-08-20 12:19:28 -07001575 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001576 """Create a new branch off the manifest's revision.
1577 """
Simran Basib9a1b732015-08-20 12:19:28 -07001578 if not branch_merge:
1579 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001580 head = self.work_git.GetHead()
1581 if head == (R_HEADS + name):
1582 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001583
David Pursehouse8a68ff92012-09-24 12:15:13 +09001584 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001585 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001586 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001587 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001588 capture_stdout=True,
1589 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001590
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591 branch = self.GetBranch(name)
1592 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001593 branch.merge = branch_merge
1594 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1595 branch.merge = R_HEADS + branch_merge
natalie.chen9a61a912017-05-26 16:35:48 +08001596 '''make sure local merge branch exists'''
1597 self._CreateLocalMergeBranch(branch.LocalMerge, self.revisionExpr)
1598
David Pursehouse8a68ff92012-09-24 12:15:13 +09001599 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001600
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001601 if head.startswith(R_HEADS):
1602 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001603 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001604 except KeyError:
1605 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001606 if revid and head and revid == head:
1607 ref = os.path.join(self.gitdir, R_HEADS + name)
1608 try:
1609 os.makedirs(os.path.dirname(ref))
1610 except OSError:
1611 pass
1612 _lwrite(ref, '%s\n' % revid)
1613 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1614 'ref: %s%s\n' % (R_HEADS, name))
1615 branch.Save()
1616 return True
1617
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001618 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001619 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001620 capture_stdout=True,
1621 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001622 branch.Save()
1623 return True
1624 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001625
Wink Saville02d79452009-04-10 13:01:24 -07001626 def CheckoutBranch(self, name):
1627 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001628
1629 Args:
1630 name: The name of the branch to checkout.
1631
1632 Returns:
1633 True if the checkout succeeded; False if it didn't; None if the branch
1634 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001635 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001636 rev = R_HEADS + name
1637 head = self.work_git.GetHead()
1638 if head == rev:
1639 # Already on the branch
1640 #
1641 return True
Wink Saville02d79452009-04-10 13:01:24 -07001642
David Pursehouse8a68ff92012-09-24 12:15:13 +09001643 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001644 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001645 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001646 except KeyError:
1647 # Branch does not exist in this project
1648 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001649 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001650
1651 if head.startswith(R_HEADS):
1652 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001653 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001654 except KeyError:
1655 head = None
1656
1657 if head == revid:
1658 # Same revision; just update HEAD to point to the new
1659 # target branch, but otherwise take no other action.
1660 #
1661 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1662 'ref: %s%s\n' % (R_HEADS, name))
1663 return True
Wink Saville02d79452009-04-10 13:01:24 -07001664
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001665 return GitCommand(self,
1666 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001667 capture_stdout=True,
1668 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001669
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001670 def AbandonBranch(self, name):
1671 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001672
1673 Args:
1674 name: The name of the branch to abandon.
1675
1676 Returns:
1677 True if the abandon succeeded; False if it didn't; None if the branch
1678 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001679 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001680 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001681 all_refs = self.bare_ref.all
1682 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001683 # Doesn't exist
1684 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001685
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001686 head = self.work_git.GetHead()
1687 if head == rev:
1688 # We can't destroy the branch while we are sitting
1689 # on it. Switch to a detached HEAD.
1690 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001691 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001692
David Pursehouse8a68ff92012-09-24 12:15:13 +09001693 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001694 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001695 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1696 '%s\n' % revid)
1697 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001698 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001699
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001700 return GitCommand(self,
1701 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001702 capture_stdout=True,
1703 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 def PruneHeads(self):
1706 """Prune any topic branches already merged into upstream.
1707 """
1708 cb = self.CurrentBranch
1709 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001710 left = self._allrefs
1711 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712 if name.startswith(R_HEADS):
1713 name = name[len(R_HEADS):]
1714 if cb is None or name != cb:
1715 kill.append(name)
1716
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001717 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718 if cb is not None \
1719 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001720 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001721 self.work_git.DetachHead(HEAD)
1722 kill.append(cb)
1723
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001725 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727 try:
1728 self.bare_git.DetachHead(rev)
1729
1730 b = ['branch', '-d']
1731 b.extend(kill)
1732 b = GitCommand(self, b, bare=True,
1733 capture_stdout=True,
1734 capture_stderr=True)
1735 b.Wait()
1736 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001737 if ID_RE.match(old):
1738 self.bare_git.DetachHead(old)
1739 else:
1740 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001741 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001743 for branch in kill:
1744 if (R_HEADS + branch) not in left:
1745 self.CleanPublishedCache()
1746 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747
1748 if cb and cb not in kill:
1749 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001750 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751
1752 kept = []
1753 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001754 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755 branch = self.GetBranch(branch)
1756 base = branch.LocalMerge
1757 if not base:
1758 base = rev
1759 kept.append(ReviewableBranch(self, branch, base))
1760 return kept
1761
1762
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001763# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001764
1765 def GetRegisteredSubprojects(self):
1766 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001767
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001768 def rec(subprojects):
1769 if not subprojects:
1770 return
1771 result.extend(subprojects)
1772 for p in subprojects:
1773 rec(p.subprojects)
1774 rec(self.subprojects)
1775 return result
1776
1777 def _GetSubmodules(self):
1778 # Unfortunately we cannot call `git submodule status --recursive` here
1779 # because the working tree might not exist yet, and it cannot be used
1780 # without a working tree in its current implementation.
1781
1782 def get_submodules(gitdir, rev):
1783 # Parse .gitmodules for submodule sub_paths and sub_urls
1784 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1785 if not sub_paths:
1786 return []
1787 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1788 # revision of submodule repository
1789 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1790 submodules = []
1791 for sub_path, sub_url in zip(sub_paths, sub_urls):
1792 try:
1793 sub_rev = sub_revs[sub_path]
1794 except KeyError:
1795 # Ignore non-exist submodules
1796 continue
1797 submodules.append((sub_rev, sub_path, sub_url))
1798 return submodules
1799
1800 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1801 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001802
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001803 def parse_gitmodules(gitdir, rev):
1804 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1805 try:
Anthony King7bdac712014-07-16 12:56:40 +01001806 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1807 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001808 except GitError:
1809 return [], []
1810 if p.Wait() != 0:
1811 return [], []
1812
1813 gitmodules_lines = []
1814 fd, temp_gitmodules_path = tempfile.mkstemp()
1815 try:
1816 os.write(fd, p.stdout)
1817 os.close(fd)
1818 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001819 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1820 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001821 if p.Wait() != 0:
1822 return [], []
1823 gitmodules_lines = p.stdout.split('\n')
1824 except GitError:
1825 return [], []
1826 finally:
1827 os.remove(temp_gitmodules_path)
1828
1829 names = set()
1830 paths = {}
1831 urls = {}
1832 for line in gitmodules_lines:
1833 if not line:
1834 continue
1835 m = re_path.match(line)
1836 if m:
1837 names.add(m.group(1))
1838 paths[m.group(1)] = m.group(2)
1839 continue
1840 m = re_url.match(line)
1841 if m:
1842 names.add(m.group(1))
1843 urls[m.group(1)] = m.group(2)
1844 continue
1845 names = sorted(names)
1846 return ([paths.get(name, '') for name in names],
1847 [urls.get(name, '') for name in names])
1848
1849 def git_ls_tree(gitdir, rev, paths):
1850 cmd = ['ls-tree', rev, '--']
1851 cmd.extend(paths)
1852 try:
Anthony King7bdac712014-07-16 12:56:40 +01001853 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1854 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001855 except GitError:
1856 return []
1857 if p.Wait() != 0:
1858 return []
1859 objects = {}
1860 for line in p.stdout.split('\n'):
1861 if not line.strip():
1862 continue
1863 object_rev, object_path = line.split()[2:4]
1864 objects[object_path] = object_rev
1865 return objects
1866
1867 try:
1868 rev = self.GetRevisionId()
1869 except GitError:
1870 return []
1871 return get_submodules(self.gitdir, rev)
1872
1873 def GetDerivedSubprojects(self):
1874 result = []
1875 if not self.Exists:
1876 # If git repo does not exist yet, querying its submodules will
1877 # mess up its states; so return here.
1878 return result
1879 for rev, path, url in self._GetSubmodules():
1880 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001881 relpath, worktree, gitdir, objdir = \
1882 self.manifest.GetSubprojectPaths(self, name, path)
1883 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001884 if project:
1885 result.extend(project.GetDerivedSubprojects())
1886 continue
David James8d201162013-10-11 17:03:19 -07001887
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001888 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001889 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001890 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001891 review=self.remote.review,
1892 revision=self.remote.revision)
1893 subproject = Project(manifest=self.manifest,
1894 name=name,
1895 remote=remote,
1896 gitdir=gitdir,
1897 objdir=objdir,
1898 worktree=worktree,
1899 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001900 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001901 revisionId=rev,
1902 rebase=self.rebase,
1903 groups=self.groups,
1904 sync_c=self.sync_c,
1905 sync_s=self.sync_s,
1906 parent=self,
1907 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001908 result.append(subproject)
1909 result.extend(subproject.GetDerivedSubprojects())
1910 return result
1911
1912
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001913# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001914 def _CheckForSha1(self):
1915 try:
1916 # if revision (sha or tag) is not present then following function
1917 # throws an error.
1918 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1919 return True
1920 except GitError:
1921 # There is no such persistent revision. We have to fetch it.
1922 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001923
Julien Campergue335f5ef2013-10-16 11:02:35 +02001924 def _FetchArchive(self, tarpath, cwd=None):
1925 cmd = ['archive', '-v', '-o', tarpath]
1926 cmd.append('--remote=%s' % self.remote.url)
1927 cmd.append('--prefix=%s/' % self.relpath)
1928 cmd.append(self.revisionExpr)
1929
1930 command = GitCommand(self, cmd, cwd=cwd,
1931 capture_stdout=True,
1932 capture_stderr=True)
1933
1934 if command.Wait() != 0:
1935 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1936
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001937 def _RemoteFetch(self, name=None,
1938 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001939 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001940 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001941 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001942 no_tags=False,
1943 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001944
1945 is_sha1 = False
1946 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001947 depth = None
1948
1949 # The depth should not be used when fetching to a mirror because
1950 # it will result in a shallow repository that cannot be cloned or
1951 # fetched from.
1952 if not self.manifest.IsMirror:
1953 if self.clone_depth:
1954 depth = self.clone_depth
1955 else:
1956 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001957 # The repo project should never be synced with partial depth
1958 if self.relpath == '.repo/repo':
1959 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001960
Shawn Pearce69e04d82014-01-29 12:48:54 -08001961 if depth:
1962 current_branch_only = True
1963
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001964 if ID_RE.match(self.revisionExpr) is not None:
1965 is_sha1 = True
1966
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001967 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001968 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001969 # this is a tag and its sha1 value should never change
1970 tag_name = self.revisionExpr[len(R_TAGS):]
1971
1972 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001973 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001974 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001975 if is_sha1 and not depth:
1976 # When syncing a specific commit and --depth is not set:
1977 # * if upstream is explicitly specified and is not a sha1, fetch only
1978 # upstream as users expect only upstream to be fetch.
1979 # Note: The commit might not be in upstream in which case the sync
1980 # will fail.
1981 # * otherwise, fetch all branches to make sure we end up with the
1982 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001983 if self.upstream:
1984 current_branch_only = not ID_RE.match(self.upstream)
1985 else:
1986 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001987
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001988 if not name:
1989 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001990
1991 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001992 remote = self.GetRemote(name)
1993 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001994 ssh_proxy = True
1995
Shawn O. Pearce88443382010-10-08 10:02:09 +02001996 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001997 if alt_dir and 'objects' == os.path.basename(alt_dir):
1998 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001999 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2000 remote = self.GetRemote(name)
2001
David Pursehouse8a68ff92012-09-24 12:15:13 +09002002 all_refs = self.bare_ref.all
2003 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002004 tmp = set()
2005
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302006 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002007 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002008 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002009 all_refs[r] = ref_id
2010 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002011 continue
2012
David Pursehouse8a68ff92012-09-24 12:15:13 +09002013 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002014 continue
2015
David Pursehouse8a68ff92012-09-24 12:15:13 +09002016 r = 'refs/_alt/%s' % ref_id
2017 all_refs[r] = ref_id
2018 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002019 tmp.add(r)
2020
Shawn O. Pearce88443382010-10-08 10:02:09 +02002021 tmp_packed = ''
2022 old_packed = ''
2023
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302024 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002025 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 tmp_packed += line
2027 if r not in tmp:
2028 old_packed += line
2029
2030 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002031 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002032 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002033
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002034 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002035
Conley Owensf97e8382015-01-21 11:12:46 -08002036 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002037 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002038 else:
2039 # If this repo has shallow objects, then we don't know which refs have
2040 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2041 # do this with projects that don't have shallow objects, since it is less
2042 # efficient.
2043 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2044 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002045
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002046 if quiet:
2047 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002048 if not self.worktree:
2049 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002050 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002051
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002052 # If using depth then we should not get all the tags since they may
2053 # be outside of the depth.
2054 if no_tags or depth:
2055 cmd.append('--no-tags')
2056 else:
2057 cmd.append('--tags')
2058
natalie.chene8996f92015-12-29 10:53:30 +08002059 refs = []
David Pursehouse74cfd272015-10-14 10:50:15 +09002060 if prune:
2061 cmd.append('--prune')
2062
Conley Owens80b87fe2014-05-09 17:13:44 -07002063 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002064 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002065 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002066 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chene8996f92015-12-29 10:53:30 +08002067 if self.manifest.IsMirror: refs.append('refs/heads/*')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002068 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002069 spec.append('tag')
2070 spec.append(tag_name)
natalie.chene8996f92015-12-29 10:53:30 +08002071 refs.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002072
David Pursehouse403b64e2015-04-27 10:41:33 +09002073 if not self.manifest.IsMirror:
2074 branch = self.revisionExpr
natalie.chen0546a1b2016-04-09 12:17:37 +08002075 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002076 # Shallow checkout of a specific commit, fetch from that commit and not
2077 # the heads only as the commit might be deeper in the history.
natalie.chene8996f92015-12-29 10:53:30 +08002078 spec.append(branch)
David Pursehouse403b64e2015-04-27 10:41:33 +09002079 else:
2080 if is_sha1:
2081 branch = self.upstream
2082 if branch is not None and branch.strip():
2083 if not branch.startswith('refs/'):
2084 branch = R_HEADS + branch
2085 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
natalie.chene8996f92015-12-29 10:53:30 +08002086 refs.append(remote.ToLocal(branch))
Conley Owens80b87fe2014-05-09 17:13:44 -07002087 cmd.extend(spec)
2088
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002089 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002090 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002091 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002092 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002093 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002094 ok = True
2095 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002096 # If needed, run the 'git remote prune' the first time through the loop
2097 elif (not _i and
2098 "error:" in gitcmd.stderr and
2099 "git remote prune" in gitcmd.stderr):
2100 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002101 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002102 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002103 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002104 break
2105 continue
Brian Harring14a66742012-09-28 20:21:57 -07002106 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002107 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2108 # in sha1 mode, we just tried sync'ing from the upstream field; it
2109 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002110 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002111 elif ret < 0:
2112 # Git died with a signal, exit immediately
2113 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002114 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002115
2116 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002117 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002118 if old_packed != '':
2119 _lwrite(packed_refs, old_packed)
2120 else:
2121 os.remove(packed_refs)
2122 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002123
2124 if is_sha1 and current_branch_only and self.upstream:
2125 # We just synced the upstream given branch; verify we
2126 # got what we wanted, else trigger a second run of all
2127 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002128 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002129 if not depth:
2130 # Avoid infinite recursion when depth is True (since depth implies
2131 # current_branch_only)
2132 return self._RemoteFetch(name=name, current_branch_only=False,
2133 initial=False, quiet=quiet, alt_dir=alt_dir)
2134 if self.clone_depth:
2135 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002136 return self._RemoteFetch(name=name,
2137 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002138 initial=False, quiet=quiet, alt_dir=alt_dir)
natalie.chene8996f92015-12-29 10:53:30 +08002139 if self.lfs_fetch:
2140 self.__FetchLfsObjects(name, refs)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002141 return ok
2142
2143 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002144 if initial and \
2145 (self.manifest.manifestProject.config.GetString('repo.depth') or
2146 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002147 return False
2148
2149 remote = self.GetRemote(self.remote.name)
2150 bundle_url = remote.url + '/clone.bundle'
2151 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002152 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2153 'persistent-http',
2154 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002155 return False
2156
2157 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2158 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002159
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002160 exist_dst = os.path.exists(bundle_dst)
2161 exist_tmp = os.path.exists(bundle_tmp)
2162
2163 if not initial and not exist_dst and not exist_tmp:
2164 return False
2165
2166 if not exist_dst:
2167 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2168 if not exist_dst:
2169 return False
2170
2171 cmd = ['fetch']
2172 if quiet:
2173 cmd.append('--quiet')
2174 if not self.worktree:
2175 cmd.append('--update-head-ok')
2176 cmd.append(bundle_dst)
2177 for f in remote.fetch:
2178 cmd.append(str(f))
2179 cmd.append('refs/tags/*:refs/tags/*')
2180
2181 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002182 if os.path.exists(bundle_dst):
2183 os.remove(bundle_dst)
2184 if os.path.exists(bundle_tmp):
2185 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002186 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002187
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002188 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002189 if os.path.exists(dstPath):
2190 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002191
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002192 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002193 if quiet:
2194 cmd += ['--silent']
2195 if os.path.exists(tmpPath):
2196 size = os.stat(tmpPath).st_size
2197 if size >= 1024:
2198 cmd += ['--continue-at', '%d' % (size,)]
2199 else:
2200 os.remove(tmpPath)
2201 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2202 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002203 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002204 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002205 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002206 if srcUrl.startswith('persistent-'):
2207 srcUrl = srcUrl[len('persistent-'):]
2208 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002209
Dave Borowitz137d0132015-01-02 11:12:54 -08002210 if IsTrace():
2211 Trace('%s', ' '.join(cmd))
2212 try:
2213 proc = subprocess.Popen(cmd)
2214 except OSError:
2215 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002216
Dave Borowitz137d0132015-01-02 11:12:54 -08002217 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002218
Dave Borowitz137d0132015-01-02 11:12:54 -08002219 if curlret == 22:
2220 # From curl man page:
2221 # 22: HTTP page not retrieved. The requested url was not found or
2222 # returned another error with the HTTP error code being 400 or above.
2223 # This return code only appears if -f, --fail is used.
2224 if not quiet:
2225 print("Server does not provide clone.bundle; ignoring.",
2226 file=sys.stderr)
2227 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002228
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002229 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002230 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002231 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002232 return True
2233 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002234 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002235 return False
2236 else:
2237 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002238
Kris Giesingc8d882a2014-12-23 13:02:32 -08002239 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002240 try:
2241 with open(path) as f:
2242 if f.read(16) == '# v2 git bundle\n':
2243 return True
2244 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002245 if not quiet:
2246 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002247 return False
2248 except OSError:
2249 return False
2250
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002251 def _Checkout(self, rev, quiet=False):
2252 cmd = ['checkout']
2253 if quiet:
2254 cmd.append('-q')
2255 cmd.append(rev)
2256 cmd.append('--')
2257 if GitCommand(self, cmd).Wait() != 0:
2258 if self._allrefs:
2259 raise GitError('%s checkout %s ' % (self.name, rev))
2260
Anthony King7bdac712014-07-16 12:56:40 +01002261 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002262 cmd = ['cherry-pick']
2263 cmd.append(rev)
2264 cmd.append('--')
2265 if GitCommand(self, cmd).Wait() != 0:
2266 if self._allrefs:
2267 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2268
Anthony King7bdac712014-07-16 12:56:40 +01002269 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002270 cmd = ['revert']
2271 cmd.append('--no-edit')
2272 cmd.append(rev)
2273 cmd.append('--')
2274 if GitCommand(self, cmd).Wait() != 0:
2275 if self._allrefs:
2276 raise GitError('%s revert %s ' % (self.name, rev))
2277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002278 def _ResetHard(self, rev, quiet=True):
2279 cmd = ['reset', '--hard']
2280 if quiet:
2281 cmd.append('-q')
2282 cmd.append(rev)
2283 if GitCommand(self, cmd).Wait() != 0:
2284 raise GitError('%s reset --hard %s ' % (self.name, rev))
2285
Anthony King7bdac712014-07-16 12:56:40 +01002286 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002287 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 if onto is not None:
2289 cmd.extend(['--onto', onto])
2290 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002291 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002292 raise GitError('%s rebase %s ' % (self.name, upstream))
2293
Pierre Tardy3d125942012-05-04 12:18:12 +02002294 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002295 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002296 if ffonly:
2297 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002298 if GitCommand(self, cmd).Wait() != 0:
2299 raise GitError('%s merge %s ' % (self.name, head))
2300
Kevin Degiabaa7f32014-11-12 11:27:45 -07002301 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002302 init_git_dir = not os.path.exists(self.gitdir)
2303 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002304 try:
2305 # Initialize the bare repository, which contains all of the objects.
2306 if init_obj_dir:
2307 os.makedirs(self.objdir)
2308 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002309
Kevin Degib1a07b82015-07-27 13:33:43 -06002310 # If we have a separate directory to hold refs, initialize it as well.
2311 if self.objdir != self.gitdir:
2312 if init_git_dir:
2313 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002314
Kevin Degib1a07b82015-07-27 13:33:43 -06002315 if init_obj_dir or init_git_dir:
2316 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2317 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002318 try:
2319 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2320 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002321 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002322 print("Retrying clone after deleting %s" %
2323 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002324 try:
2325 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002326 if self.worktree and os.path.exists(os.path.realpath
2327 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002328 shutil.rmtree(os.path.realpath(self.worktree))
2329 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2330 except:
2331 raise e
2332 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002333
Kevin Degib1a07b82015-07-27 13:33:43 -06002334 if init_git_dir:
2335 mp = self.manifest.manifestProject
2336 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002337
Kevin Degib1a07b82015-07-27 13:33:43 -06002338 if ref_dir or mirror_git:
2339 if not mirror_git:
2340 mirror_git = os.path.join(ref_dir, self.name + '.git')
2341 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2342 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002343
Kevin Degib1a07b82015-07-27 13:33:43 -06002344 if os.path.exists(mirror_git):
2345 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002346
Kevin Degib1a07b82015-07-27 13:33:43 -06002347 elif os.path.exists(repo_git):
2348 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002349
Kevin Degib1a07b82015-07-27 13:33:43 -06002350 else:
2351 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002352
Kevin Degib1a07b82015-07-27 13:33:43 -06002353 if ref_dir:
2354 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2355 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002356
Kevin Degib1a07b82015-07-27 13:33:43 -06002357 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002358
Kevin Degib1a07b82015-07-27 13:33:43 -06002359 m = self.manifest.manifestProject.config
2360 for key in ['user.name', 'user.email']:
2361 if m.Has(key, include_defaults=False):
natalie.chen507a9362018-02-14 17:52:40 +08002362 self.config.SetString(key, m.GetString(key))
Kevin Degib1a07b82015-07-27 13:33:43 -06002363 if self.manifest.IsMirror:
2364 self.config.SetString('core.bare', 'true')
2365 else:
2366 self.config.SetString('core.bare', None)
natalie.chene8996f92015-12-29 10:53:30 +08002367
natalie.chen4e5600a2016-01-13 11:51:40 +08002368 if self.lfs_fetch:
2369 self.config.SetString('filter.lfs.clean', 'git-lfs clean %f')
2370 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge %f')
2371 self.config.SetString('filter.lfs.required', 'true')
natalie.chene8996f92015-12-29 10:53:30 +08002372
Kevin Degib1a07b82015-07-27 13:33:43 -06002373 except Exception:
2374 if init_obj_dir and os.path.exists(self.objdir):
2375 shutil.rmtree(self.objdir)
2376 if init_git_dir and os.path.exists(self.gitdir):
2377 shutil.rmtree(self.gitdir)
2378 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002379
Jimmie Westera0444582012-10-24 13:44:42 +02002380 def _UpdateHooks(self):
2381 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002382 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002384 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002385 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002386 if not os.path.exists(hooks):
2387 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002388 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002389 name = os.path.basename(stock_hook)
2390
Victor Boivie65e0f352011-04-18 11:23:29 +02002391 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002392 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002393 # Don't install a Gerrit Code Review hook if this
2394 # project does not appear to use it for reviews.
2395 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002396 # Since the manifest project is one of those, but also
2397 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002398 continue
2399
2400 dst = os.path.join(hooks, name)
2401 if os.path.islink(dst):
2402 continue
2403 if os.path.exists(dst):
2404 if filecmp.cmp(stock_hook, dst, shallow=False):
2405 os.remove(dst)
2406 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002407 _warn("%s: Not replacing locally modified %s hook",
2408 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002409 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002410 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002411 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002412 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002413 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002414 raise GitError('filesystem must support symlinks')
2415 else:
2416 raise
2417
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002418 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002419 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002420 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002421 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002422 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002423 remote.review = self.remote.review
2424 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002425
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002426 if self.worktree:
2427 remote.ResetFetch(mirror=False)
2428 else:
2429 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002430 remote.Save()
2431
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432 def _InitMRef(self):
2433 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002434 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002436 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002437 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002438
2439 def _InitAnyMRef(self, ref):
2440 cur = self.bare_ref.symref(ref)
2441
2442 if self.revisionId:
2443 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2444 msg = 'manifest set to %s' % self.revisionId
2445 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002446 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002447 else:
2448 remote = self.GetRemote(self.remote.name)
2449 dst = remote.ToLocal(self.revisionExpr)
2450 if cur != dst:
2451 msg = 'manifest set to %s' % self.revisionExpr
2452 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002453
Kevin Degi384b3c52014-10-16 16:02:58 -06002454 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002455 symlink_files = self.shareable_files[:]
2456 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002457 if share_refs:
2458 symlink_files += self.working_tree_files
2459 symlink_dirs += self.working_tree_dirs
2460 to_symlink = symlink_files + symlink_dirs
2461 for name in set(to_symlink):
2462 dst = os.path.realpath(os.path.join(destdir, name))
2463 if os.path.lexists(dst):
2464 src = os.path.realpath(os.path.join(srcdir, name))
2465 # Fail if the links are pointing to the wrong place
2466 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002467 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002468 'work tree. If you\'re comfortable with the '
2469 'possibility of losing the work tree\'s git metadata,'
2470 ' use `repo sync --force-sync {0}` to '
2471 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002472
David James8d201162013-10-11 17:03:19 -07002473 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2474 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2475
2476 Args:
2477 gitdir: The bare git repository. Must already be initialized.
2478 dotgit: The repository you would like to initialize.
2479 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2480 Only one work tree can store refs under a given |gitdir|.
2481 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2482 This saves you the effort of initializing |dotgit| yourself.
2483 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002484 symlink_files = self.shareable_files[:]
2485 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002486 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002487 symlink_files += self.working_tree_files
2488 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002489 to_symlink = symlink_files + symlink_dirs
2490
2491 to_copy = []
2492 if copy_all:
2493 to_copy = os.listdir(gitdir)
2494
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002495 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002496 for name in set(to_copy).union(to_symlink):
2497 try:
2498 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002499 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002500
Kevin Degi384b3c52014-10-16 16:02:58 -06002501 if os.path.lexists(dst):
2502 continue
David James8d201162013-10-11 17:03:19 -07002503
2504 # If the source dir doesn't exist, create an empty dir.
2505 if name in symlink_dirs and not os.path.lexists(src):
2506 os.makedirs(src)
2507
Conley Owens80b87fe2014-05-09 17:13:44 -07002508 # If the source file doesn't exist, ensure the destination
2509 # file doesn't either.
2510 if name in symlink_files and not os.path.lexists(src):
2511 try:
2512 os.remove(dst)
2513 except OSError:
2514 pass
2515
David James8d201162013-10-11 17:03:19 -07002516 if name in to_symlink:
2517 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2518 elif copy_all and not os.path.islink(dst):
2519 if os.path.isdir(src):
2520 shutil.copytree(src, dst)
2521 elif os.path.isfile(src):
2522 shutil.copy(src, dst)
2523 except OSError as e:
2524 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002525 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002526 else:
2527 raise
2528
Kevin Degiabaa7f32014-11-12 11:27:45 -07002529 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002530 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002531 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002532 try:
2533 if init_dotgit:
2534 os.makedirs(dotgit)
2535 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2536 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537
Kevin Degiabaa7f32014-11-12 11:27:45 -07002538 try:
2539 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2540 except GitError as e:
2541 if force_sync:
2542 try:
2543 shutil.rmtree(dotgit)
2544 return self._InitWorkTree(force_sync=False)
2545 except:
2546 raise e
2547 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002548
Kevin Degib1a07b82015-07-27 13:33:43 -06002549 if init_dotgit:
2550 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002551
Kevin Degib1a07b82015-07-27 13:33:43 -06002552 cmd = ['read-tree', '--reset', '-u']
2553 cmd.append('-v')
2554 cmd.append(HEAD)
2555 if GitCommand(self, cmd).Wait() != 0:
2556 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002557
Kevin Degib1a07b82015-07-27 13:33:43 -06002558 self._CopyAndLinkFiles()
2559 except Exception:
2560 if init_dotgit:
2561 shutil.rmtree(dotgit)
2562 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002563
2564 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002565 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002567 def _revlist(self, *args, **kw):
2568 a = []
2569 a.extend(args)
2570 a.append('--')
2571 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002572
2573 @property
2574 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002575 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002576
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002577 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002578 """Get logs between two revisions of this project."""
2579 comp = '..'
2580 if rev1:
2581 revs = [rev1]
2582 if rev2:
2583 revs.extend([comp, rev2])
2584 cmd = ['log', ''.join(revs)]
2585 out = DiffColoring(self.config)
2586 if out.is_on and color:
2587 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002588 if pretty_format is not None:
2589 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002590 if oneline:
2591 cmd.append('--oneline')
2592
2593 try:
2594 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2595 if log.Wait() == 0:
2596 return log.stdout
2597 except GitError:
2598 # worktree may not exist if groups changed for example. In that case,
2599 # try in gitdir instead.
2600 if not os.path.exists(self.worktree):
2601 return self.bare_git.log(*cmd[1:])
2602 else:
2603 raise
2604 return None
2605
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002606 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2607 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002608 """Get the list of logs from this revision to given revisionId"""
2609 logs = {}
2610 selfId = self.GetRevisionId(self._allrefs)
2611 toId = toProject.GetRevisionId(toProject._allrefs)
2612
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002613 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2614 pretty_format=pretty_format)
2615 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2616 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002617 return logs
2618
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002619 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002620
David James8d201162013-10-11 17:03:19 -07002621 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622 self._project = project
2623 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002624 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002625
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002626 def LsOthers(self):
2627 p = GitCommand(self._project,
2628 ['ls-files',
2629 '-z',
2630 '--others',
2631 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002632 bare=False,
David James8d201162013-10-11 17:03:19 -07002633 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002634 capture_stdout=True,
2635 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636 if p.Wait() == 0:
2637 out = p.stdout
2638 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002639 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002640 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002641 return []
2642
2643 def DiffZ(self, name, *args):
2644 cmd = [name]
2645 cmd.append('-z')
2646 cmd.extend(args)
2647 p = GitCommand(self._project,
2648 cmd,
David James8d201162013-10-11 17:03:19 -07002649 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002650 bare=False,
2651 capture_stdout=True,
2652 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002653 try:
2654 out = p.process.stdout.read()
2655 r = {}
2656 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002657 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002658 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002659 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002660 info = next(out)
2661 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002662 except StopIteration:
2663 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002664
2665 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002666
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002667 def __init__(self, path, omode, nmode, oid, nid, state):
2668 self.path = path
2669 self.src_path = None
2670 self.old_mode = omode
2671 self.new_mode = nmode
2672 self.old_id = oid
2673 self.new_id = nid
2674
2675 if len(state) == 1:
2676 self.status = state
2677 self.level = None
2678 else:
2679 self.status = state[:1]
2680 self.level = state[1:]
2681 while self.level.startswith('0'):
2682 self.level = self.level[1:]
2683
2684 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002685 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 if info.status in ('R', 'C'):
2687 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002688 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689 r[info.path] = info
2690 return r
2691 finally:
2692 p.Wait()
2693
2694 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002695 if self._bare:
2696 path = os.path.join(self._project.gitdir, HEAD)
2697 else:
2698 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002699 try:
2700 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002701 except IOError as e:
2702 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002703 try:
2704 line = fd.read()
2705 finally:
2706 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302707 try:
2708 line = line.decode()
2709 except AttributeError:
2710 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002711 if line.startswith('ref: '):
2712 return line[5:-1]
2713 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002714
2715 def SetHead(self, ref, message=None):
2716 cmdv = []
2717 if message is not None:
2718 cmdv.extend(['-m', message])
2719 cmdv.append(HEAD)
2720 cmdv.append(ref)
2721 self.symbolic_ref(*cmdv)
2722
2723 def DetachHead(self, new, message=None):
2724 cmdv = ['--no-deref']
2725 if message is not None:
2726 cmdv.extend(['-m', message])
2727 cmdv.append(HEAD)
2728 cmdv.append(new)
2729 self.update_ref(*cmdv)
2730
2731 def UpdateRef(self, name, new, old=None,
2732 message=None,
2733 detach=False):
2734 cmdv = []
2735 if message is not None:
2736 cmdv.extend(['-m', message])
2737 if detach:
2738 cmdv.append('--no-deref')
2739 cmdv.append(name)
2740 cmdv.append(new)
2741 if old is not None:
2742 cmdv.append(old)
2743 self.update_ref(*cmdv)
2744
2745 def DeleteRef(self, name, old=None):
2746 if not old:
2747 old = self.rev_parse(name)
2748 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002749 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002751 def rev_list(self, *args, **kw):
2752 if 'format' in kw:
2753 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2754 else:
2755 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002756 cmdv.extend(args)
2757 p = GitCommand(self._project,
2758 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002759 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002760 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002761 capture_stdout=True,
2762 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002763 r = []
2764 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002765 if line[-1] == '\n':
2766 line = line[:-1]
2767 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002769 raise GitError('%s rev-list %s: %s' %
2770 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002771 return r
2772
2773 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002774 """Allow arbitrary git commands using pythonic syntax.
2775
2776 This allows you to do things like:
2777 git_obj.rev_parse('HEAD')
2778
2779 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2780 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002781 Any other positional arguments will be passed to the git command, and the
2782 following keyword arguments are supported:
2783 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002784
2785 Args:
2786 name: The name of the git command to call. Any '_' characters will
2787 be replaced with '-'.
2788
2789 Returns:
2790 A callable object that will try to call git with the named command.
2791 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002792 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002793
Dave Borowitz091f8932012-10-23 17:01:04 -07002794 def runner(*args, **kwargs):
2795 cmdv = []
2796 config = kwargs.pop('config', None)
2797 for k in kwargs:
2798 raise TypeError('%s() got an unexpected keyword argument %r'
2799 % (name, k))
2800 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002801 if not git_require((1, 7, 2)):
2802 raise ValueError('cannot set config on command line for %s()'
2803 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302804 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002805 cmdv.append('-c')
2806 cmdv.append('%s=%s' % (k, v))
2807 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002808 cmdv.extend(args)
2809 p = GitCommand(self._project,
2810 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002811 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002812 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002813 capture_stdout=True,
2814 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002815 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002816 raise GitError('%s %s: %s' %
2817 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002818 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302819 try:
Conley Owensedd01512013-09-26 12:59:58 -07002820 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302821 except AttributeError:
2822 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002823 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2824 return r[:-1]
2825 return r
2826 return runner
2827
2828
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002829class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002830
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002831 def __str__(self):
2832 return 'prior sync failed; rebase still in progress'
2833
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002834
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002835class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002836
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002837 def __str__(self):
2838 return 'contains uncommitted changes'
2839
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002840
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002841class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002842
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002843 def __init__(self, project, text):
2844 self.project = project
2845 self.text = text
2846
2847 def Print(self, syncbuf):
2848 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2849 syncbuf.out.nl()
2850
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002851
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002852class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002853
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002854 def __init__(self, project, why):
2855 self.project = project
2856 self.why = why
2857
2858 def Print(self, syncbuf):
2859 syncbuf.out.fail('error: %s/: %s',
2860 self.project.relpath,
2861 str(self.why))
2862 syncbuf.out.nl()
2863
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002864
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002865class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002866
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002867 def __init__(self, project, action):
2868 self.project = project
2869 self.action = action
2870
2871 def Run(self, syncbuf):
2872 out = syncbuf.out
2873 out.project('project %s/', self.project.relpath)
2874 out.nl()
2875 try:
2876 self.action()
2877 out.nl()
2878 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002879 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002880 out.nl()
2881 return False
2882
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002883
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002884class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002885
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002886 def __init__(self, config):
2887 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002888 self.project = self.printer('header', attr='bold')
2889 self.info = self.printer('info')
2890 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002891
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002892
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002893class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002894
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002895 def __init__(self, config, detach_head=False):
2896 self._messages = []
2897 self._failures = []
2898 self._later_queue1 = []
2899 self._later_queue2 = []
2900
2901 self.out = _SyncColoring(config)
2902 self.out.redirect(sys.stderr)
2903
2904 self.detach_head = detach_head
2905 self.clean = True
2906
2907 def info(self, project, fmt, *args):
2908 self._messages.append(_InfoMessage(project, fmt % args))
2909
2910 def fail(self, project, err=None):
2911 self._failures.append(_Failure(project, err))
2912 self.clean = False
2913
2914 def later1(self, project, what):
2915 self._later_queue1.append(_Later(project, what))
2916
2917 def later2(self, project, what):
2918 self._later_queue2.append(_Later(project, what))
2919
2920 def Finish(self):
2921 self._PrintMessages()
2922 self._RunLater()
2923 self._PrintMessages()
2924 return self.clean
2925
2926 def _RunLater(self):
2927 for q in ['_later_queue1', '_later_queue2']:
2928 if not self._RunQueue(q):
2929 return
2930
2931 def _RunQueue(self, queue):
2932 for m in getattr(self, queue):
2933 if not m.Run(self):
2934 self.clean = False
2935 return False
2936 setattr(self, queue, [])
2937 return True
2938
2939 def _PrintMessages(self):
2940 for m in self._messages:
2941 m.Print(self)
2942 for m in self._failures:
2943 m.Print(self)
2944
2945 self._messages = []
2946 self._failures = []
2947
2948
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002949class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002950
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002951 """A special project housed under .repo.
2952 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002953
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002955 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002956 manifest=manifest,
2957 name=name,
2958 gitdir=gitdir,
2959 objdir=gitdir,
2960 worktree=worktree,
2961 remote=RemoteSpec('origin'),
2962 relpath='.repo/%s' % name,
2963 revisionExpr='refs/heads/master',
2964 revisionId=None,
2965 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
2967 def PreSync(self):
2968 if self.Exists:
2969 cb = self.CurrentBranch
2970 if cb:
2971 base = self.GetBranch(cb).merge
2972 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002973 self.revisionExpr = base
2974 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975
Anthony King7bdac712014-07-16 12:56:40 +01002976 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002977 """ Prepare MetaProject for manifest branch switch
2978 """
2979
2980 # detach and delete manifest branch, allowing a new
2981 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002982 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002983 self.Sync_LocalHalf(syncbuf)
2984 syncbuf.Finish()
2985
2986 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002987 ['update-ref', '-d', 'refs/heads/default'],
2988 capture_stdout=True,
2989 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002990
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002991 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002992 def LastFetch(self):
2993 try:
2994 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2995 return os.path.getmtime(fh)
2996 except OSError:
2997 return 0
2998
2999 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003000 def HasChanges(self):
3001 """Has the remote received new commits not yet checked out?
3002 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003003 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003004 return False
3005
David Pursehouse8a68ff92012-09-24 12:15:13 +09003006 all_refs = self.bare_ref.all
3007 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003008 head = self.work_git.GetHead()
3009 if head.startswith(R_HEADS):
3010 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003011 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003012 except KeyError:
3013 head = None
3014
3015 if revid == head:
3016 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003017 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003018 return True
3019 return False