blob: 06606fa6500b5c13609fbcfcc4858d8a102962b0 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 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
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
201 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500202 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000203 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200204 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700205 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200206 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200207 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800208 validate_certs=True,
209 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800210 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700211 people,
Brian Harring435370c2012-07-28 15:37:04 -0700212 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500213 hashtags=hashtags,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000214 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200215 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700216 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200218 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800219 validate_certs=validate_certs,
220 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700222 def GetPublishedRefs(self):
223 refs = {}
224 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700225 self.branch.remote.SshReviewUrl(self.project.UserEmail),
226 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700227 for line in output.split('\n'):
228 try:
229 (sha, ref) = line.split()
230 refs[sha] = ref
231 except ValueError:
232 pass
233
234 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239 def __init__(self, config):
240 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100241 self.project = self.printer('header', attr='bold')
242 self.branch = self.printer('header', attr='bold')
243 self.nobranch = self.printer('nobranch', fg='red')
244 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245
Anthony King7bdac712014-07-16 12:56:40 +0100246 self.added = self.printer('added', fg='green')
247 self.changed = self.printer('changed', fg='red')
248 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
250
251class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253 def __init__(self, config):
254 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100255 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400256 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700258
Anthony King7bdac712014-07-16 12:56:40 +0100259class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
James W. Mills24c13082012-04-12 15:04:13 -0500261 def __init__(self, name, value, keep):
262 self.name = name
263 self.value = value
264 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Mike Frysingere6a202f2019-08-02 15:57:57 -0400267def _SafeExpandPath(base, subpath, skipfinal=False):
268 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700269
Mike Frysingere6a202f2019-08-02 15:57:57 -0400270 We make sure no intermediate symlinks are traversed, and that the final path
271 is not a special file (e.g. not a socket or fifo).
272
273 NB: We rely on a number of paths already being filtered out while parsing the
274 manifest. See the validation logic in manifest_xml.py for more details.
275 """
276 components = subpath.split(os.path.sep)
277 if skipfinal:
278 # Whether the caller handles the final component itself.
279 finalpart = components.pop()
280
281 path = base
282 for part in components:
283 if part in {'.', '..'}:
284 raise ManifestInvalidPathError(
285 '%s: "%s" not allowed in paths' % (subpath, part))
286
287 path = os.path.join(path, part)
288 if platform_utils.islink(path):
289 raise ManifestInvalidPathError(
290 '%s: traversing symlinks not allow' % (path,))
291
292 if os.path.exists(path):
293 if not os.path.isfile(path) and not platform_utils.isdir(path):
294 raise ManifestInvalidPathError(
295 '%s: only regular files & directories allowed' % (path,))
296
297 if skipfinal:
298 path = os.path.join(path, finalpart)
299
300 return path
301
302
303class _CopyFile(object):
304 """Container for <copyfile> manifest element."""
305
306 def __init__(self, git_worktree, src, topdir, dest):
307 """Register a <copyfile> request.
308
309 Args:
310 git_worktree: Absolute path to the git project checkout.
311 src: Relative path under |git_worktree| of file to read.
312 topdir: Absolute path to the top of the repo client checkout.
313 dest: Relative path under |topdir| of file to write.
314 """
315 self.git_worktree = git_worktree
316 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 self.src = src
318 self.dest = dest
319
320 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400321 src = _SafeExpandPath(self.git_worktree, self.src)
322 dest = _SafeExpandPath(self.topdir, self.dest)
323
324 if platform_utils.isdir(src):
325 raise ManifestInvalidPathError(
326 '%s: copying from directory not supported' % (self.src,))
327 if platform_utils.isdir(dest):
328 raise ManifestInvalidPathError(
329 '%s: copying to directory not allowed' % (self.dest,))
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 # copy file if it does not exist or is out of date
332 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
333 try:
334 # remove existing file first, since it might be read-only
335 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800336 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400337 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200338 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700339 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341 shutil.copy(src, dest)
342 # make the file read-only
343 mode = os.stat(dest)[stat.ST_MODE]
344 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
345 os.chmod(dest, mode)
346 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700347 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Anthony King7bdac712014-07-16 12:56:40 +0100350class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400351 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 def __init__(self, git_worktree, src, topdir, dest):
354 """Register a <linkfile> request.
355
356 Args:
357 git_worktree: Absolute path to the git project checkout.
358 src: Target of symlink relative to path under |git_worktree|.
359 topdir: Absolute path to the top of the repo client checkout.
360 dest: Relative path under |topdir| of symlink to create.
361 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700362 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400363 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500364 self.src = src
365 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366
Wink Saville4c426ef2015-06-03 08:05:17 -0700367 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700369 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 try:
371 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800372 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800373 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700376 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700378 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700380 _error('Cannot link file %s to %s', relSrc, absDest)
381
382 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400383 """Link the self.src & self.dest paths.
384
385 Handles wild cards on the src linking all of the files in the source in to
386 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500388 # Some people use src="." to create stable links to projects. Lets allow
389 # that but reject all other uses of "." to keep things simple.
390 if self.src == '.':
391 src = self.git_worktree
392 else:
393 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400394
395 if os.path.exists(src):
396 # Entity exists so just a simple one to one link operation.
397 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
398 # dest & src are absolute paths at this point. Make sure the target of
399 # the symlink is relative in the context of the repo client checkout.
400 relpath = os.path.relpath(src, os.path.dirname(dest))
401 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 if os.path.exists(dest) and not platform_utils.isdir(dest):
406 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700407 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 # Create a releative path from source dir to destination dir
410 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700412
413 # Get the source file name
414 srcFile = os.path.basename(absSrcFile)
415
416 # Now form the final full paths to srcFile. They will be
417 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419 relSrc = os.path.join(relSrcDir, srcFile)
420 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500421
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700422
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700423class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425 def __init__(self,
426 name,
Anthony King7bdac712014-07-16 12:56:40 +0100427 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700428 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100429 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700430 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700431 orig_name=None,
432 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 self.name = name
434 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700435 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100437 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700438 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700439 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700440
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700441
Doug Anderson37282b42011-03-04 11:54:18 -0800442class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700443
Doug Anderson37282b42011-03-04 11:54:18 -0800444 """A RepoHook contains information about a script to run as a hook.
445
446 Hooks are used to run a python script before running an upload (for instance,
447 to run presubmit checks). Eventually, we may have hooks for other actions.
448
449 This shouldn't be confused with files in the 'repo/hooks' directory. Those
450 files are copied into each '.git/hooks' folder for each project. Repo-level
451 hooks are associated instead with repo actions.
452
453 Hooks are always python. When a hook is run, we will load the hook into the
454 interpreter and execute its main() function.
455 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700456
Doug Anderson37282b42011-03-04 11:54:18 -0800457 def __init__(self,
458 hook_type,
459 hooks_project,
460 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400461 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800462 abort_if_user_denies=False):
463 """RepoHook constructor.
464
465 Params:
466 hook_type: A string representing the type of hook. This is also used
467 to figure out the name of the file containing the hook. For
468 example: 'pre-upload'.
469 hooks_project: The project containing the repo hooks. If you have a
470 manifest, this is manifest.repo_hooks_project. OK if this is None,
471 which will make the hook a no-op.
472 topdir: Repo's top directory (the one containing the .repo directory).
473 Scripts will run with CWD as this directory. If you have a manifest,
474 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400475 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800476 abort_if_user_denies: If True, we'll throw a HookError() if the user
477 doesn't allow us to run the hook.
478 """
479 self._hook_type = hook_type
480 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400481 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800482 self._topdir = topdir
483 self._abort_if_user_denies = abort_if_user_denies
484
485 # Store the full path to the script for convenience.
486 if self._hooks_project:
487 self._script_fullpath = os.path.join(self._hooks_project.worktree,
488 self._hook_type + '.py')
489 else:
490 self._script_fullpath = None
491
492 def _GetHash(self):
493 """Return a hash of the contents of the hooks directory.
494
495 We'll just use git to do this. This hash has the property that if anything
496 changes in the directory we will return a different has.
497
498 SECURITY CONSIDERATION:
499 This hash only represents the contents of files in the hook directory, not
500 any other files imported or called by hooks. Changes to imported files
501 can change the script behavior without affecting the hash.
502
503 Returns:
504 A string representing the hash. This will always be ASCII so that it can
505 be printed to the user easily.
506 """
507 assert self._hooks_project, "Must have hooks to calculate their hash."
508
509 # We will use the work_git object rather than just calling GetRevisionId().
510 # That gives us a hash of the latest checked in version of the files that
511 # the user will actually be executing. Specifically, GetRevisionId()
512 # doesn't appear to change even if a user checks out a different version
513 # of the hooks repo (via git checkout) nor if a user commits their own revs.
514 #
515 # NOTE: Local (non-committed) changes will not be factored into this hash.
516 # I think this is OK, since we're really only worried about warning the user
517 # about upstream changes.
518 return self._hooks_project.work_git.rev_parse('HEAD')
519
520 def _GetMustVerb(self):
521 """Return 'must' if the hook is required; 'should' if not."""
522 if self._abort_if_user_denies:
523 return 'must'
524 else:
525 return 'should'
526
527 def _CheckForHookApproval(self):
528 """Check to see whether this hook has been approved.
529
Mike Frysinger40252c22016-08-15 21:23:44 -0400530 We'll accept approval of manifest URLs if they're using secure transports.
531 This way the user can say they trust the manifest hoster. For insecure
532 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800533
534 Note that we ask permission for each individual hook even though we use
535 the hash of all hooks when detecting changes. We'd like the user to be
536 able to approve / deny each hook individually. We only use the hash of all
537 hooks because there is no other easy way to detect changes to local imports.
538
539 Returns:
540 True if this hook is approved to run; False otherwise.
541
542 Raises:
543 HookError: Raised if the user doesn't approve and abort_if_user_denies
544 was passed to the consturctor.
545 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400546 if self._ManifestUrlHasSecureScheme():
547 return self._CheckForHookApprovalManifest()
548 else:
549 return self._CheckForHookApprovalHash()
550
551 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
552 changed_prompt):
553 """Check for approval for a particular attribute and hook.
554
555 Args:
556 subkey: The git config key under [repo.hooks.<hook_type>] to store the
557 last approved string.
558 new_val: The new value to compare against the last approved one.
559 main_prompt: Message to display to the user to ask for approval.
560 changed_prompt: Message explaining why we're re-asking for approval.
561
562 Returns:
563 True if this hook is approved to run; False otherwise.
564
565 Raises:
566 HookError: Raised if the user doesn't approve and abort_if_user_denies
567 was passed to the consturctor.
568 """
Doug Anderson37282b42011-03-04 11:54:18 -0800569 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400570 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800571
Mike Frysinger40252c22016-08-15 21:23:44 -0400572 # Get the last value that the user approved for this hook; may be None.
573 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800574
Mike Frysinger40252c22016-08-15 21:23:44 -0400575 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800576 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400577 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800578 # Approval matched. We're done.
579 return True
580 else:
581 # Give the user a reason why we're prompting, since they last told
582 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400583 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800584 else:
585 prompt = ''
586
587 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
588 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400589 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530590 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900591 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800592
593 # User is doing a one-time approval.
594 if response in ('y', 'yes'):
595 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400596 elif response == 'always':
597 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800598 return True
599
600 # For anything else, we'll assume no approval.
601 if self._abort_if_user_denies:
602 raise HookError('You must allow the %s hook or use --no-verify.' %
603 self._hook_type)
604
605 return False
606
Mike Frysinger40252c22016-08-15 21:23:44 -0400607 def _ManifestUrlHasSecureScheme(self):
608 """Check if the URI for the manifest is a secure transport."""
609 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
610 parse_results = urllib.parse.urlparse(self._manifest_url)
611 return parse_results.scheme in secure_schemes
612
613 def _CheckForHookApprovalManifest(self):
614 """Check whether the user has approved this manifest host.
615
616 Returns:
617 True if this hook is approved to run; False otherwise.
618 """
619 return self._CheckForHookApprovalHelper(
620 'approvedmanifest',
621 self._manifest_url,
622 'Run hook scripts from %s' % (self._manifest_url,),
623 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
624
625 def _CheckForHookApprovalHash(self):
626 """Check whether the user has approved the hooks repo.
627
628 Returns:
629 True if this hook is approved to run; False otherwise.
630 """
631 prompt = ('Repo %s run the script:\n'
632 ' %s\n'
633 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700634 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400635 return self._CheckForHookApprovalHelper(
636 'approvedhash',
637 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700638 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400639 'Scripts have changed since %s was allowed.' % (self._hook_type,))
640
Mike Frysingerf7c51602019-06-18 17:23:39 -0400641 @staticmethod
642 def _ExtractInterpFromShebang(data):
643 """Extract the interpreter used in the shebang.
644
645 Try to locate the interpreter the script is using (ignoring `env`).
646
647 Args:
648 data: The file content of the script.
649
650 Returns:
651 The basename of the main script interpreter, or None if a shebang is not
652 used or could not be parsed out.
653 """
654 firstline = data.splitlines()[:1]
655 if not firstline:
656 return None
657
658 # The format here can be tricky.
659 shebang = firstline[0].strip()
660 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
661 if not m:
662 return None
663
664 # If the using `env`, find the target program.
665 interp = m.group(1)
666 if os.path.basename(interp) == 'env':
667 interp = m.group(2)
668
669 return interp
670
671 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
672 """Execute the hook script through |interp|.
673
674 Note: Support for this feature should be dropped ~Jun 2021.
675
676 Args:
677 interp: The Python program to run.
678 context: Basic Python context to execute the hook inside.
679 kwargs: Arbitrary arguments to pass to the hook script.
680
681 Raises:
682 HookError: When the hooks failed for any reason.
683 """
684 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
685 script = """
686import json, os, sys
687path = '''%(path)s'''
688kwargs = json.loads('''%(kwargs)s''')
689context = json.loads('''%(context)s''')
690sys.path.insert(0, os.path.dirname(path))
691data = open(path).read()
692exec(compile(data, path, 'exec'), context)
693context['main'](**kwargs)
694""" % {
695 'path': self._script_fullpath,
696 'kwargs': json.dumps(kwargs),
697 'context': json.dumps(context),
698 }
699
700 # We pass the script via stdin to avoid OS argv limits. It also makes
701 # unhandled exception tracebacks less verbose/confusing for users.
702 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
703 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
704 proc.communicate(input=script.encode('utf-8'))
705 if proc.returncode:
706 raise HookError('Failed to run %s hook.' % (self._hook_type,))
707
708 def _ExecuteHookViaImport(self, data, context, **kwargs):
709 """Execute the hook code in |data| directly.
710
711 Args:
712 data: The code of the hook to execute.
713 context: Basic Python context to execute the hook inside.
714 kwargs: Arbitrary arguments to pass to the hook script.
715
716 Raises:
717 HookError: When the hooks failed for any reason.
718 """
719 # Exec, storing global context in the context dict. We catch exceptions
720 # and convert to a HookError w/ just the failing traceback.
721 try:
722 exec(compile(data, self._script_fullpath, 'exec'), context)
723 except Exception:
724 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
725 (traceback.format_exc(), self._hook_type))
726
727 # Running the script should have defined a main() function.
728 if 'main' not in context:
729 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
730
731 # Call the main function in the hook. If the hook should cause the
732 # build to fail, it will raise an Exception. We'll catch that convert
733 # to a HookError w/ just the failing traceback.
734 try:
735 context['main'](**kwargs)
736 except Exception:
737 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
738 'above.' % (traceback.format_exc(), self._hook_type))
739
Doug Anderson37282b42011-03-04 11:54:18 -0800740 def _ExecuteHook(self, **kwargs):
741 """Actually execute the given hook.
742
743 This will run the hook's 'main' function in our python interpreter.
744
745 Args:
746 kwargs: Keyword arguments to pass to the hook. These are often specific
747 to the hook type. For instance, pre-upload hooks will contain
748 a project_list.
749 """
750 # Keep sys.path and CWD stashed away so that we can always restore them
751 # upon function exit.
752 orig_path = os.getcwd()
753 orig_syspath = sys.path
754
755 try:
756 # Always run hooks with CWD as topdir.
757 os.chdir(self._topdir)
758
759 # Put the hook dir as the first item of sys.path so hooks can do
760 # relative imports. We want to replace the repo dir as [0] so
761 # hooks can't import repo files.
762 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
763
Mike Frysingerf7c51602019-06-18 17:23:39 -0400764 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500765 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800766
Doug Anderson37282b42011-03-04 11:54:18 -0800767 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
768 # We don't actually want hooks to define their main with this argument--
769 # it's there to remind them that their hook should always take **kwargs.
770 # For instance, a pre-upload hook should be defined like:
771 # def main(project_list, **kwargs):
772 #
773 # This allows us to later expand the API without breaking old hooks.
774 kwargs = kwargs.copy()
775 kwargs['hook_should_take_kwargs'] = True
776
Mike Frysingerf7c51602019-06-18 17:23:39 -0400777 # See what version of python the hook has been written against.
778 data = open(self._script_fullpath).read()
779 interp = self._ExtractInterpFromShebang(data)
780 reexec = False
781 if interp:
782 prog = os.path.basename(interp)
783 if prog.startswith('python2') and sys.version_info.major != 2:
784 reexec = True
785 elif prog.startswith('python3') and sys.version_info.major == 2:
786 reexec = True
787
788 # Attempt to execute the hooks through the requested version of Python.
789 if reexec:
790 try:
791 self._ExecuteHookViaReexec(interp, context, **kwargs)
792 except OSError as e:
793 if e.errno == errno.ENOENT:
794 # We couldn't find the interpreter, so fallback to importing.
795 reexec = False
796 else:
797 raise
798
799 # Run the hook by importing directly.
800 if not reexec:
801 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800802 finally:
803 # Restore sys.path and CWD.
804 sys.path = orig_syspath
805 os.chdir(orig_path)
806
807 def Run(self, user_allows_all_hooks, **kwargs):
808 """Run the hook.
809
810 If the hook doesn't exist (because there is no hooks project or because
811 this particular hook is not enabled), this is a no-op.
812
813 Args:
814 user_allows_all_hooks: If True, we will never prompt about running the
815 hook--we'll just assume it's OK to run it.
816 kwargs: Keyword arguments to pass to the hook. These are often specific
817 to the hook type. For instance, pre-upload hooks will contain
818 a project_list.
819
820 Raises:
821 HookError: If there was a problem finding the hook or the user declined
822 to run a required hook (from _CheckForHookApproval).
823 """
824 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700825 if ((not self._hooks_project) or (self._hook_type not in
826 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800827 return
828
829 # Bail with a nice error if we can't find the hook.
830 if not os.path.isfile(self._script_fullpath):
831 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
832
833 # Make sure the user is OK with running the hook.
834 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
835 return
836
837 # Run the hook with the same version of python we're using.
838 self._ExecuteHook(**kwargs)
839
840
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600842 # These objects can be shared between several working trees.
843 shareable_files = ['description', 'info']
844 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
845 # These objects can only be used by a single working tree.
846 working_tree_files = ['config', 'packed-refs', 'shallow']
847 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700848
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 def __init__(self,
850 manifest,
851 name,
852 remote,
853 gitdir,
David James8d201162013-10-11 17:03:19 -0700854 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 worktree,
856 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700857 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800858 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100859 rebase=True,
860 groups=None,
861 sync_c=False,
862 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900863 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100864 clone_depth=None,
865 upstream=None,
866 parent=None,
867 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900868 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700869 optimized_fetch=False,
870 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800871 """Init a Project object.
872
873 Args:
874 manifest: The XmlManifest object.
875 name: The `name` attribute of manifest.xml's project element.
876 remote: RemoteSpec object specifying its remote's properties.
877 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700878 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800879 worktree: Absolute path of git working tree.
880 relpath: Relative path of git working tree to repo's top directory.
881 revisionExpr: The `revision` attribute of manifest.xml's project element.
882 revisionId: git commit id for checking out.
883 rebase: The `rebase` attribute of manifest.xml's project element.
884 groups: The `groups` attribute of manifest.xml's project element.
885 sync_c: The `sync-c` attribute of manifest.xml's project element.
886 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900887 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800888 upstream: The `upstream` attribute of manifest.xml's project element.
889 parent: The parent Project object.
890 is_derived: False if the project was explicitly defined in the manifest;
891 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400892 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900893 optimized_fetch: If True, when a project is set to a sha1 revision, only
894 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700895 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800896 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 self.manifest = manifest
898 self.name = name
899 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800900 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700901 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800902 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700903 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800904 else:
905 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700907 self.revisionExpr = revisionExpr
908
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700909 if revisionId is None \
910 and revisionExpr \
911 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700912 self.revisionId = revisionExpr
913 else:
914 self.revisionId = revisionId
915
Mike Pontillod3153822012-02-28 11:53:24 -0800916 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700917 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700918 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800919 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900920 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900921 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700922 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800923 self.parent = parent
924 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900925 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800926 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800927
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500930 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500931 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700932 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
933 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800935 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700936 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800937 else:
938 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700939 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700940 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700941 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400942 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700943 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944
Doug Anderson37282b42011-03-04 11:54:18 -0800945 # This will be filled in if a project is later identified to be the
946 # project containing repo hooks.
947 self.enabled_repo_hooks = []
948
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800950 def Derived(self):
951 return self.is_derived
952
953 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700955 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
957 @property
958 def CurrentBranch(self):
959 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400960
961 The branch name omits the 'refs/heads/' prefix.
962 None is returned if the project is on a detached HEAD, or if the work_git is
963 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400965 try:
966 b = self.work_git.GetHead()
967 except NoManifestException:
968 # If the local checkout is in a bad state, don't barf. Let the callers
969 # process this like the head is unreadable.
970 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 if b.startswith(R_HEADS):
972 return b[len(R_HEADS):]
973 return None
974
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700975 def IsRebaseInProgress(self):
976 w = self.worktree
977 g = os.path.join(w, '.git')
978 return os.path.exists(os.path.join(g, 'rebase-apply')) \
979 or os.path.exists(os.path.join(g, 'rebase-merge')) \
980 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200981
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 def IsDirty(self, consider_untracked=True):
983 """Is the working directory modified in some way?
984 """
985 self.work_git.update_index('-q',
986 '--unmerged',
987 '--ignore-missing',
988 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900989 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 return True
991 if self.work_git.DiffZ('diff-files'):
992 return True
993 if consider_untracked and self.work_git.LsOthers():
994 return True
995 return False
996
997 _userident_name = None
998 _userident_email = None
999
1000 @property
1001 def UserName(self):
1002 """Obtain the user's personal name.
1003 """
1004 if self._userident_name is None:
1005 self._LoadUserIdentity()
1006 return self._userident_name
1007
1008 @property
1009 def UserEmail(self):
1010 """Obtain the user's email address. This is very likely
1011 to be their Gerrit login.
1012 """
1013 if self._userident_email is None:
1014 self._LoadUserIdentity()
1015 return self._userident_email
1016
1017 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001018 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1019 m = re.compile("^(.*) <([^>]*)> ").match(u)
1020 if m:
1021 self._userident_name = m.group(1)
1022 self._userident_email = m.group(2)
1023 else:
1024 self._userident_name = ''
1025 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026
1027 def GetRemote(self, name):
1028 """Get the configuration for a single remote.
1029 """
1030 return self.config.GetRemote(name)
1031
1032 def GetBranch(self, name):
1033 """Get the configuration for a single branch.
1034 """
1035 return self.config.GetBranch(name)
1036
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001037 def GetBranches(self):
1038 """Get all existing local branches.
1039 """
1040 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001041 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001042 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001043
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301044 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001045 if name.startswith(R_HEADS):
1046 name = name[len(R_HEADS):]
1047 b = self.GetBranch(name)
1048 b.current = name == current
1049 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001050 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001051 heads[name] = b
1052
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301053 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001054 if name.startswith(R_PUB):
1055 name = name[len(R_PUB):]
1056 b = heads.get(name)
1057 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001058 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001059
1060 return heads
1061
Colin Cross5acde752012-03-28 20:15:45 -07001062 def MatchesGroups(self, manifest_groups):
1063 """Returns true if the manifest groups specified at init should cause
1064 this project to be synced.
1065 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001066 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001067
1068 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001069 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001070 manifest_groups: "-group1,group2"
1071 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001072
1073 The special manifest group "default" will match any project that
1074 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001075 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001076 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001077 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001078 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001079 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001080
Conley Owens971de8e2012-04-16 10:36:08 -07001081 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001082 for group in expanded_manifest_groups:
1083 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001084 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001085 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001086 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001087
Conley Owens971de8e2012-04-16 10:36:08 -07001088 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001090# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001091 def UncommitedFiles(self, get_all=True):
1092 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001094 Args:
1095 get_all: a boolean, if True - get information about all different
1096 uncommitted files. If False - return as soon as any kind of
1097 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001098 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001099 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001100 self.work_git.update_index('-q',
1101 '--unmerged',
1102 '--ignore-missing',
1103 '--refresh')
1104 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001105 details.append("rebase in progress")
1106 if not get_all:
1107 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001108
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001109 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1110 if changes:
1111 details.extend(changes)
1112 if not get_all:
1113 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001114
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001115 changes = self.work_git.DiffZ('diff-files').keys()
1116 if changes:
1117 details.extend(changes)
1118 if not get_all:
1119 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001120
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001121 changes = self.work_git.LsOthers()
1122 if changes:
1123 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001124
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001125 return details
1126
1127 def HasChanges(self):
1128 """Returns true if there are uncommitted changes.
1129 """
1130 if self.UncommitedFiles(get_all=False):
1131 return True
1132 else:
1133 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001134
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001135 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001137
1138 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001139 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001140 quiet: If True then only print the project name. Do not print
1141 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001143 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001144 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001145 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001146 print(file=output_redir)
1147 print('project %s/' % self.relpath, file=output_redir)
1148 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 return
1150
1151 self.work_git.update_index('-q',
1152 '--unmerged',
1153 '--ignore-missing',
1154 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001155 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1157 df = self.work_git.DiffZ('diff-files')
1158 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001159 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001160 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001161
1162 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001163 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001164 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001165 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001167 if quiet:
1168 out.nl()
1169 return 'DIRTY'
1170
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171 branch = self.CurrentBranch
1172 if branch is None:
1173 out.nobranch('(*** NO BRANCH ***)')
1174 else:
1175 out.branch('branch %s', branch)
1176 out.nl()
1177
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001178 if rb:
1179 out.important('prior sync failed; rebase still in progress')
1180 out.nl()
1181
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182 paths = list()
1183 paths.extend(di.keys())
1184 paths.extend(df.keys())
1185 paths.extend(do)
1186
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301187 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001188 try:
1189 i = di[p]
1190 except KeyError:
1191 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001193 try:
1194 f = df[p]
1195 except KeyError:
1196 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001197
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001198 if i:
1199 i_status = i.status.upper()
1200 else:
1201 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001203 if f:
1204 f_status = f.status.lower()
1205 else:
1206 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001207
1208 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001209 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001210 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001211 else:
1212 line = ' %s%s\t%s' % (i_status, f_status, p)
1213
1214 if i and not f:
1215 out.added('%s', line)
1216 elif (i and f) or (not i and f):
1217 out.changed('%s', line)
1218 elif not i and not f:
1219 out.untracked('%s', line)
1220 else:
1221 out.write('%s', line)
1222 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001223
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001224 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225
pelyad67872d2012-03-28 14:49:58 +03001226 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 """Prints the status of the repository to stdout.
1228 """
1229 out = DiffColoring(self.config)
1230 cmd = ['diff']
1231 if out.is_on:
1232 cmd.append('--color')
1233 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001234 if absolute_paths:
1235 cmd.append('--src-prefix=a/%s/' % self.relpath)
1236 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001238 try:
1239 p = GitCommand(self,
1240 cmd,
1241 capture_stdout=True,
1242 capture_stderr=True)
1243 except GitError as e:
1244 out.nl()
1245 out.project('project %s/' % self.relpath)
1246 out.nl()
1247 out.fail('%s', str(e))
1248 out.nl()
1249 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001250 has_diff = False
1251 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001252 if not hasattr(line, 'encode'):
1253 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254 if not has_diff:
1255 out.nl()
1256 out.project('project %s/' % self.relpath)
1257 out.nl()
1258 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001259 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001260 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001262# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001263 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001264 """Was the branch published (uploaded) for code review?
1265 If so, returns the SHA-1 hash of the last published
1266 state for the branch.
1267 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001268 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001269 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001270 try:
1271 return self.bare_git.rev_parse(key)
1272 except GitError:
1273 return None
1274 else:
1275 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001276 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277 except KeyError:
1278 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279
David Pursehouse8a68ff92012-09-24 12:15:13 +09001280 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 """Prunes any stale published refs.
1282 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001283 if all_refs is None:
1284 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285 heads = set()
1286 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301287 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288 if name.startswith(R_HEADS):
1289 heads.add(name)
1290 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301293 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 n = name[len(R_PUB):]
1295 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001296 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001298 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 """List any branches which can be uploaded for review.
1300 """
1301 heads = {}
1302 pubed = {}
1303
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301304 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001306 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001308 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309
1310 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301311 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001312 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001314 if selected_branch and branch != selected_branch:
1315 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001317 rb = self.GetUploadableBranch(branch)
1318 if rb:
1319 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 return ready
1321
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001322 def GetUploadableBranch(self, branch_name):
1323 """Get a single uploadable branch, or None.
1324 """
1325 branch = self.GetBranch(branch_name)
1326 base = branch.LocalMerge
1327 if branch.LocalMerge:
1328 rb = ReviewableBranch(self, branch, base)
1329 if rb.commits:
1330 return rb
1331 return None
1332
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001333 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001334 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001335 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001336 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001337 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001338 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001339 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001340 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001341 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001342 validate_certs=True,
1343 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 """Uploads the named branch for code review.
1345 """
1346 if branch is None:
1347 branch = self.CurrentBranch
1348 if branch is None:
1349 raise GitError('not currently on a branch')
1350
1351 branch = self.GetBranch(branch)
1352 if not branch.LocalMerge:
1353 raise GitError('branch %s does not track a remote' % branch.name)
1354 if not branch.remote.review:
1355 raise GitError('remote %s has no review url' % branch.remote.name)
1356
Bryan Jacobsf609f912013-05-06 13:36:24 -04001357 if dest_branch is None:
1358 dest_branch = self.dest_branch
1359 if dest_branch is None:
1360 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361 if not dest_branch.startswith(R_HEADS):
1362 dest_branch = R_HEADS + dest_branch
1363
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001364 if not branch.remote.projectname:
1365 branch.remote.projectname = self.name
1366 branch.remote.Save()
1367
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001368 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001369 if url is None:
1370 raise UploadError('review not configured')
1371 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001372
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001373 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001374 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001375
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001376 for push_option in (push_options or []):
1377 cmd.append('-o')
1378 cmd.append(push_option)
1379
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001380 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001381
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001382 if dest_branch.startswith(R_HEADS):
1383 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001384
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001385 upload_type = 'for'
1386 if draft:
1387 upload_type = 'drafts'
1388
1389 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1390 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001391 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001392 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001393 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001394 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001395
David Pursehousef25a3702018-11-14 19:01:22 -08001396 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001397 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001398 if notify:
1399 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001400 if private:
1401 opts += ['private']
1402 if wip:
1403 opts += ['wip']
1404 if opts:
1405 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001406 cmd.append(ref_spec)
1407
Anthony King7bdac712014-07-16 12:56:40 +01001408 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001409 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001410
1411 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1412 self.bare_git.UpdateRef(R_PUB + branch.name,
1413 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001414 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001416# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001417 def _ExtractArchive(self, tarpath, path=None):
1418 """Extract the given tar on its current location
1419
1420 Args:
1421 - tarpath: The path to the actual tar file
1422
1423 """
1424 try:
1425 with tarfile.open(tarpath, 'r') as tar:
1426 tar.extractall(path=path)
1427 return True
1428 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001429 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001430 return False
1431
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001432 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001433 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001434 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001435 is_new=None,
1436 current_branch_only=False,
1437 force_sync=False,
1438 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001439 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001440 archive=False,
1441 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001442 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001443 submodules=False,
1444 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445 """Perform only the network IO portion of the sync process.
1446 Local working directory/branch state is not affected.
1447 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001448 if archive and not isinstance(self, MetaProject):
1449 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001450 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001451 return False
1452
1453 name = self.relpath.replace('\\', '/')
1454 name = name.replace('/', '_')
1455 tarpath = '%s.tar' % name
1456 topdir = self.manifest.topdir
1457
1458 try:
1459 self._FetchArchive(tarpath, cwd=topdir)
1460 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001461 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001462 return False
1463
1464 # From now on, we only need absolute tarpath
1465 tarpath = os.path.join(topdir, tarpath)
1466
1467 if not self._ExtractArchive(tarpath, path=topdir):
1468 return False
1469 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001470 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001471 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001472 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001473 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001474 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001475 if is_new is None:
1476 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001477 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001478 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001479 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001480 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001481 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001482
1483 if is_new:
1484 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1485 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001486 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001487 # This works for both absolute and relative alternate directories.
1488 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001489 except IOError:
1490 alt_dir = None
1491 else:
1492 alt_dir = None
1493
Mike Frysingere50b6a72020-02-19 01:45:48 -05001494 if (clone_bundle
1495 and alt_dir is None
1496 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001497 is_new = False
1498
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001499 if not current_branch_only:
1500 if self.sync_c:
1501 current_branch_only = True
1502 elif not self.manifest._loaded:
1503 # Manifest cannot check defaults until it syncs.
1504 current_branch_only = False
1505 elif self.manifest.default.sync_c:
1506 current_branch_only = True
1507
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001508 if not self.sync_tags:
1509 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001510
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001511 if self.clone_depth:
1512 depth = self.clone_depth
1513 else:
1514 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1515
Mike Frysinger521d01b2020-02-17 01:51:49 -05001516 # See if we can skip the network fetch entirely.
1517 if not (optimized_fetch and
1518 (ID_RE.match(self.revisionExpr) and
1519 self._CheckForImmutableRevision())):
1520 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001521 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1522 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001523 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001524 submodules=submodules, force_sync=force_sync,
1525 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001526 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001527
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001528 mp = self.manifest.manifestProject
1529 dissociate = mp.config.GetBoolean('repo.dissociate')
1530 if dissociate:
1531 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1532 if os.path.exists(alternates_file):
1533 cmd = ['repack', '-a', '-d']
1534 if GitCommand(self, cmd, bare=True).Wait() != 0:
1535 return False
1536 platform_utils.remove(alternates_file)
1537
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001538 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001539 self._InitMRef()
1540 else:
1541 self._InitMirrorHead()
1542 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001543 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001544 except OSError:
1545 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001546 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001547
1548 def PostRepoUpgrade(self):
1549 self._InitHooks()
1550
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001551 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001552 if self.manifest.isGitcClient:
1553 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001554 for copyfile in self.copyfiles:
1555 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001556 for linkfile in self.linkfiles:
1557 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001558
Julien Camperguedd654222014-01-09 16:21:37 +01001559 def GetCommitRevisionId(self):
1560 """Get revisionId of a commit.
1561
1562 Use this method instead of GetRevisionId to get the id of the commit rather
1563 than the id of the current git object (for example, a tag)
1564
1565 """
1566 if not self.revisionExpr.startswith(R_TAGS):
1567 return self.GetRevisionId(self._allrefs)
1568
1569 try:
1570 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1571 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001572 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1573 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001574
David Pursehouse8a68ff92012-09-24 12:15:13 +09001575 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001576 if self.revisionId:
1577 return self.revisionId
1578
1579 rem = self.GetRemote(self.remote.name)
1580 rev = rem.ToLocal(self.revisionExpr)
1581
David Pursehouse8a68ff92012-09-24 12:15:13 +09001582 if all_refs is not None and rev in all_refs:
1583 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001584
1585 try:
1586 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1587 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001588 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1589 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001590
Martin Kellye4e94d22017-03-21 16:05:12 -07001591 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592 """Perform only the local IO portion of the sync process.
1593 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001595 if not os.path.exists(self.gitdir):
1596 syncbuf.fail(self,
1597 'Cannot checkout %s due to missing network sync; Run '
1598 '`repo sync -n %s` first.' %
1599 (self.name, self.name))
1600 return
1601
Martin Kellye4e94d22017-03-21 16:05:12 -07001602 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001603 all_refs = self.bare_ref.all
1604 self.CleanPublishedCache(all_refs)
1605 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001606
David Pursehouse1d947b32012-10-25 12:23:11 +09001607 def _doff():
1608 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001609 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001610
Martin Kellye4e94d22017-03-21 16:05:12 -07001611 def _dosubmodules():
1612 self._SyncSubmodules(quiet=True)
1613
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001614 head = self.work_git.GetHead()
1615 if head.startswith(R_HEADS):
1616 branch = head[len(R_HEADS):]
1617 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001619 except KeyError:
1620 head = None
1621 else:
1622 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001623
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001624 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001625 # Currently on a detached HEAD. The user is assumed to
1626 # not have any local modifications worth worrying about.
1627 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001628 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001629 syncbuf.fail(self, _PriorSyncFailedError())
1630 return
1631
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001632 if head == revid:
1633 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001634 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001635 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001636 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001637 # The copy/linkfile config may have changed.
1638 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001639 return
1640 else:
1641 lost = self._revlist(not_rev(revid), HEAD)
1642 if lost:
1643 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001646 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001647 if submodules:
1648 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001649 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001650 syncbuf.fail(self, e)
1651 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001652 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001653 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001655 if head == revid:
1656 # No changes; don't do anything further.
1657 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001658 # The copy/linkfile config may have changed.
1659 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001660 return
1661
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001664 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001666 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001668 syncbuf.info(self,
1669 "leaving %s; does not track upstream",
1670 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001672 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001673 if submodules:
1674 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001675 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001676 syncbuf.fail(self, e)
1677 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001678 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001679 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001680
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001681 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001682
1683 # See if we can perform a fast forward merge. This can happen if our
1684 # branch isn't in the exact same state as we last published.
1685 try:
1686 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1687 # Skip the published logic.
1688 pub = False
1689 except GitError:
1690 pub = self.WasPublished(branch.name, all_refs)
1691
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001693 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 if not_merged:
1695 if upstream_gain:
1696 # The user has published this branch and some of those
1697 # commits are not yet merged upstream. We do not want
1698 # to rewrite the published commits so we punt.
1699 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001700 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001701 "branch %s is published (but not merged) and is now "
1702 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001703 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001704 elif pub == head:
1705 # All published commits are merged, and thus we are a
1706 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001707 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001708 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001709 if submodules:
1710 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001711 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001713 # Examine the local commits not in the remote. Find the
1714 # last one attributed to this user, if any.
1715 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001716 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001717 last_mine = None
1718 cnt_mine = 0
1719 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001720 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001721 if committer_email == self.UserEmail:
1722 last_mine = commit_id
1723 cnt_mine += 1
1724
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001725 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001726 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
1728 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001729 syncbuf.fail(self, _DirtyError())
1730 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001732 # If the upstream switched on us, warn the user.
1733 #
1734 if branch.merge != self.revisionExpr:
1735 if branch.merge and self.revisionExpr:
1736 syncbuf.info(self,
1737 'manifest switched %s...%s',
1738 branch.merge,
1739 self.revisionExpr)
1740 elif branch.merge:
1741 syncbuf.info(self,
1742 'manifest no longer tracks %s',
1743 branch.merge)
1744
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001745 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001747 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001749 syncbuf.info(self,
1750 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001751 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001753 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001754 if not ID_RE.match(self.revisionExpr):
1755 # in case of manifest sync the revisionExpr might be a SHA1
1756 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001757 if not branch.merge.startswith('refs/'):
1758 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001759 branch.Save()
1760
Mike Pontillod3153822012-02-28 11:53:24 -08001761 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001762 def _docopyandlink():
1763 self._CopyAndLinkFiles()
1764
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001765 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001766 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001767 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001768 if submodules:
1769 syncbuf.later2(self, _dosubmodules)
1770 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001771 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001773 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001774 if submodules:
1775 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001776 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001777 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001778 syncbuf.fail(self, e)
1779 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001781 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001782 if submodules:
1783 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784
Mike Frysingere6a202f2019-08-02 15:57:57 -04001785 def AddCopyFile(self, src, dest, topdir):
1786 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787
Mike Frysingere6a202f2019-08-02 15:57:57 -04001788 No filesystem changes occur here. Actual copying happens later on.
1789
1790 Paths should have basic validation run on them before being queued.
1791 Further checking will be handled when the actual copy happens.
1792 """
1793 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1794
1795 def AddLinkFile(self, src, dest, topdir):
1796 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1797
1798 No filesystem changes occur here. Actual linking happens later on.
1799
1800 Paths should have basic validation run on them before being queued.
1801 Further checking will be handled when the actual link happens.
1802 """
1803 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001804
James W. Mills24c13082012-04-12 15:04:13 -05001805 def AddAnnotation(self, name, value, keep):
1806 self.annotations.append(_Annotation(name, value, keep))
1807
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001808 def DownloadPatchSet(self, change_id, patch_id):
1809 """Download a single patch set of a single change to FETCH_HEAD.
1810 """
1811 remote = self.GetRemote(self.remote.name)
1812
1813 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001814 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001815 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001816 if GitCommand(self, cmd, bare=True).Wait() != 0:
1817 return None
1818 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001819 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001820 change_id,
1821 patch_id,
1822 self.bare_git.rev_parse('FETCH_HEAD'))
1823
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001824# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001825 def GetHeadPath(self):
1826 """Return the full path to the HEAD ref."""
1827 dotgit = os.path.join(self.worktree, '.git')
1828 if os.path.isfile(dotgit):
1829 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1830 with open(dotgit) as fp:
1831 setting = fp.read()
1832 assert setting.startswith('gitdir:')
1833 gitdir = setting.split(':', 1)[1].strip()
1834 dotgit = os.path.join(self.worktree, gitdir)
1835 return os.path.join(dotgit, HEAD)
1836
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001837 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001838 """Create a new branch off the manifest's revision.
1839 """
Simran Basib9a1b732015-08-20 12:19:28 -07001840 if not branch_merge:
1841 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001842 head = self.work_git.GetHead()
1843 if head == (R_HEADS + name):
1844 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001845
David Pursehouse8a68ff92012-09-24 12:15:13 +09001846 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001847 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001848 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001849 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001850 capture_stdout=True,
1851 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001852
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001853 branch = self.GetBranch(name)
1854 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001855 branch.merge = branch_merge
1856 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1857 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001858
1859 if revision is None:
1860 revid = self.GetRevisionId(all_refs)
1861 else:
1862 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001863
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001864 if head.startswith(R_HEADS):
1865 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001866 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001867 except KeyError:
1868 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001869 if revid and head and revid == head:
1870 ref = os.path.join(self.gitdir, R_HEADS + name)
1871 try:
1872 os.makedirs(os.path.dirname(ref))
1873 except OSError:
1874 pass
1875 _lwrite(ref, '%s\n' % revid)
Mike Frysingerf914edc2020-02-09 03:01:56 -05001876 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001877 branch.Save()
1878 return True
1879
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001880 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001881 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001882 capture_stdout=True,
1883 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001884 branch.Save()
1885 return True
1886 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001887
Wink Saville02d79452009-04-10 13:01:24 -07001888 def CheckoutBranch(self, name):
1889 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001890
1891 Args:
1892 name: The name of the branch to checkout.
1893
1894 Returns:
1895 True if the checkout succeeded; False if it didn't; None if the branch
1896 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001897 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001898 rev = R_HEADS + name
1899 head = self.work_git.GetHead()
1900 if head == rev:
1901 # Already on the branch
1902 #
1903 return True
Wink Saville02d79452009-04-10 13:01:24 -07001904
David Pursehouse8a68ff92012-09-24 12:15:13 +09001905 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001906 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001907 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001908 except KeyError:
1909 # Branch does not exist in this project
1910 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001911 return None
Wink Saville02d79452009-04-10 13:01:24 -07001912
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001913 if head.startswith(R_HEADS):
1914 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001915 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001916 except KeyError:
1917 head = None
1918
1919 if head == revid:
1920 # Same revision; just update HEAD to point to the new
1921 # target branch, but otherwise take no other action.
1922 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001923 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001924 return True
1925
1926 return GitCommand(self,
1927 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001928 capture_stdout=True,
1929 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001930
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001931 def AbandonBranch(self, name):
1932 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001933
1934 Args:
1935 name: The name of the branch to abandon.
1936
1937 Returns:
1938 True if the abandon succeeded; False if it didn't; None if the branch
1939 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001940 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001941 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001942 all_refs = self.bare_ref.all
1943 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001944 # Doesn't exist
1945 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001946
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001947 head = self.work_git.GetHead()
1948 if head == rev:
1949 # We can't destroy the branch while we are sitting
1950 # on it. Switch to a detached HEAD.
1951 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001952 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001953
David Pursehouse8a68ff92012-09-24 12:15:13 +09001954 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001955 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05001956 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001957 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001958 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001959
1960 return GitCommand(self,
1961 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001962 capture_stdout=True,
1963 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001964
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001965 def PruneHeads(self):
1966 """Prune any topic branches already merged into upstream.
1967 """
1968 cb = self.CurrentBranch
1969 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001970 left = self._allrefs
1971 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972 if name.startswith(R_HEADS):
1973 name = name[len(R_HEADS):]
1974 if cb is None or name != cb:
1975 kill.append(name)
1976
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001977 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001978 if cb is not None \
1979 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001980 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001981 self.work_git.DetachHead(HEAD)
1982 kill.append(cb)
1983
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001984 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001985 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001986
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001987 try:
1988 self.bare_git.DetachHead(rev)
1989
1990 b = ['branch', '-d']
1991 b.extend(kill)
1992 b = GitCommand(self, b, bare=True,
1993 capture_stdout=True,
1994 capture_stderr=True)
1995 b.Wait()
1996 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001997 if ID_RE.match(old):
1998 self.bare_git.DetachHead(old)
1999 else:
2000 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002001 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002002
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002003 for branch in kill:
2004 if (R_HEADS + branch) not in left:
2005 self.CleanPublishedCache()
2006 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
2008 if cb and cb not in kill:
2009 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002010 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002011
2012 kept = []
2013 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002014 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002015 branch = self.GetBranch(branch)
2016 base = branch.LocalMerge
2017 if not base:
2018 base = rev
2019 kept.append(ReviewableBranch(self, branch, base))
2020 return kept
2021
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002022# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002023 def GetRegisteredSubprojects(self):
2024 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002025
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002026 def rec(subprojects):
2027 if not subprojects:
2028 return
2029 result.extend(subprojects)
2030 for p in subprojects:
2031 rec(p.subprojects)
2032 rec(self.subprojects)
2033 return result
2034
2035 def _GetSubmodules(self):
2036 # Unfortunately we cannot call `git submodule status --recursive` here
2037 # because the working tree might not exist yet, and it cannot be used
2038 # without a working tree in its current implementation.
2039
2040 def get_submodules(gitdir, rev):
2041 # Parse .gitmodules for submodule sub_paths and sub_urls
2042 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2043 if not sub_paths:
2044 return []
2045 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2046 # revision of submodule repository
2047 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2048 submodules = []
2049 for sub_path, sub_url in zip(sub_paths, sub_urls):
2050 try:
2051 sub_rev = sub_revs[sub_path]
2052 except KeyError:
2053 # Ignore non-exist submodules
2054 continue
2055 submodules.append((sub_rev, sub_path, sub_url))
2056 return submodules
2057
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002058 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2059 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002060
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002061 def parse_gitmodules(gitdir, rev):
2062 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2063 try:
Anthony King7bdac712014-07-16 12:56:40 +01002064 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2065 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002066 except GitError:
2067 return [], []
2068 if p.Wait() != 0:
2069 return [], []
2070
2071 gitmodules_lines = []
2072 fd, temp_gitmodules_path = tempfile.mkstemp()
2073 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002074 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002075 os.close(fd)
2076 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002077 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2078 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002079 if p.Wait() != 0:
2080 return [], []
2081 gitmodules_lines = p.stdout.split('\n')
2082 except GitError:
2083 return [], []
2084 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002085 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002086
2087 names = set()
2088 paths = {}
2089 urls = {}
2090 for line in gitmodules_lines:
2091 if not line:
2092 continue
2093 m = re_path.match(line)
2094 if m:
2095 names.add(m.group(1))
2096 paths[m.group(1)] = m.group(2)
2097 continue
2098 m = re_url.match(line)
2099 if m:
2100 names.add(m.group(1))
2101 urls[m.group(1)] = m.group(2)
2102 continue
2103 names = sorted(names)
2104 return ([paths.get(name, '') for name in names],
2105 [urls.get(name, '') for name in names])
2106
2107 def git_ls_tree(gitdir, rev, paths):
2108 cmd = ['ls-tree', rev, '--']
2109 cmd.extend(paths)
2110 try:
Anthony King7bdac712014-07-16 12:56:40 +01002111 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2112 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002113 except GitError:
2114 return []
2115 if p.Wait() != 0:
2116 return []
2117 objects = {}
2118 for line in p.stdout.split('\n'):
2119 if not line.strip():
2120 continue
2121 object_rev, object_path = line.split()[2:4]
2122 objects[object_path] = object_rev
2123 return objects
2124
2125 try:
2126 rev = self.GetRevisionId()
2127 except GitError:
2128 return []
2129 return get_submodules(self.gitdir, rev)
2130
2131 def GetDerivedSubprojects(self):
2132 result = []
2133 if not self.Exists:
2134 # If git repo does not exist yet, querying its submodules will
2135 # mess up its states; so return here.
2136 return result
2137 for rev, path, url in self._GetSubmodules():
2138 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002139 relpath, worktree, gitdir, objdir = \
2140 self.manifest.GetSubprojectPaths(self, name, path)
2141 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002142 if project:
2143 result.extend(project.GetDerivedSubprojects())
2144 continue
David James8d201162013-10-11 17:03:19 -07002145
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002146 if url.startswith('..'):
2147 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002148 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002149 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002150 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002151 review=self.remote.review,
2152 revision=self.remote.revision)
2153 subproject = Project(manifest=self.manifest,
2154 name=name,
2155 remote=remote,
2156 gitdir=gitdir,
2157 objdir=objdir,
2158 worktree=worktree,
2159 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002160 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002161 revisionId=rev,
2162 rebase=self.rebase,
2163 groups=self.groups,
2164 sync_c=self.sync_c,
2165 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002166 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002167 parent=self,
2168 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002169 result.append(subproject)
2170 result.extend(subproject.GetDerivedSubprojects())
2171 return result
2172
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002173# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002174 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002175 try:
2176 # if revision (sha or tag) is not present then following function
2177 # throws an error.
2178 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2179 return True
2180 except GitError:
2181 # There is no such persistent revision. We have to fetch it.
2182 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002183
Julien Campergue335f5ef2013-10-16 11:02:35 +02002184 def _FetchArchive(self, tarpath, cwd=None):
2185 cmd = ['archive', '-v', '-o', tarpath]
2186 cmd.append('--remote=%s' % self.remote.url)
2187 cmd.append('--prefix=%s/' % self.relpath)
2188 cmd.append(self.revisionExpr)
2189
2190 command = GitCommand(self, cmd, cwd=cwd,
2191 capture_stdout=True,
2192 capture_stderr=True)
2193
2194 if command.Wait() != 0:
2195 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2196
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002197 def _RemoteFetch(self, name=None,
2198 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002199 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002200 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002201 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002202 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002203 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002204 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002205 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002206 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002207 force_sync=False,
2208 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002209
2210 is_sha1 = False
2211 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002212 # The depth should not be used when fetching to a mirror because
2213 # it will result in a shallow repository that cannot be cloned or
2214 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002215 # The repo project should also never be synced with partial depth.
2216 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2217 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002218
Shawn Pearce69e04d82014-01-29 12:48:54 -08002219 if depth:
2220 current_branch_only = True
2221
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002222 if ID_RE.match(self.revisionExpr) is not None:
2223 is_sha1 = True
2224
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002225 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002226 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002227 # this is a tag and its sha1 value should never change
2228 tag_name = self.revisionExpr[len(R_TAGS):]
2229
2230 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002231 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002232 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002233 print('Skipped fetching project %s (already have persistent ref)'
2234 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002235 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002236 if is_sha1 and not depth:
2237 # When syncing a specific commit and --depth is not set:
2238 # * if upstream is explicitly specified and is not a sha1, fetch only
2239 # upstream as users expect only upstream to be fetch.
2240 # Note: The commit might not be in upstream in which case the sync
2241 # will fail.
2242 # * otherwise, fetch all branches to make sure we end up with the
2243 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002244 if self.upstream:
2245 current_branch_only = not ID_RE.match(self.upstream)
2246 else:
2247 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002248
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249 if not name:
2250 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002251
2252 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002253 remote = self.GetRemote(name)
2254 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002255 ssh_proxy = True
2256
Shawn O. Pearce88443382010-10-08 10:02:09 +02002257 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002258 if alt_dir and 'objects' == os.path.basename(alt_dir):
2259 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002260 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2261 remote = self.GetRemote(name)
2262
David Pursehouse8a68ff92012-09-24 12:15:13 +09002263 all_refs = self.bare_ref.all
2264 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002265 tmp = set()
2266
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302267 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002268 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002269 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002270 all_refs[r] = ref_id
2271 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002272 continue
2273
David Pursehouse8a68ff92012-09-24 12:15:13 +09002274 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002275 continue
2276
David Pursehouse8a68ff92012-09-24 12:15:13 +09002277 r = 'refs/_alt/%s' % ref_id
2278 all_refs[r] = ref_id
2279 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002280 tmp.add(r)
2281
heping3d7bbc92017-04-12 19:51:47 +08002282 tmp_packed_lines = []
2283 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002284
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302285 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002286 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002287 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002288 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002289 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002290
heping3d7bbc92017-04-12 19:51:47 +08002291 tmp_packed = ''.join(tmp_packed_lines)
2292 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002294 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002295 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002296
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002297 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002298
Xin Li745be2e2019-06-03 11:24:30 -07002299 if clone_filter:
2300 git_require((2, 19, 0), fail=True, msg='partial clones')
2301 cmd.append('--filter=%s' % clone_filter)
2302 self.config.SetString('extensions.partialclone', self.remote.name)
2303
Conley Owensf97e8382015-01-21 11:12:46 -08002304 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002305 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002306 else:
2307 # If this repo has shallow objects, then we don't know which refs have
2308 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2309 # do this with projects that don't have shallow objects, since it is less
2310 # efficient.
2311 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2312 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002313
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002314 if quiet:
2315 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002316 if not self.worktree:
2317 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002318 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002319
Mike Frysingere57f1142019-03-18 21:27:54 -04002320 if force_sync:
2321 cmd.append('--force')
2322
David Pursehouse74cfd272015-10-14 10:50:15 +09002323 if prune:
2324 cmd.append('--prune')
2325
Martin Kellye4e94d22017-03-21 16:05:12 -07002326 if submodules:
2327 cmd.append('--recurse-submodules=on-demand')
2328
Kuang-che Wu6856f982019-11-25 12:37:55 +08002329 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002330 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002331 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002332 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002333 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002334 spec.append('tag')
2335 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002336
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302337 if self.manifest.IsMirror and not current_branch_only:
2338 branch = None
2339 else:
2340 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002341 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002342 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002343 # Shallow checkout of a specific commit, fetch from that commit and not
2344 # the heads only as the commit might be deeper in the history.
2345 spec.append(branch)
2346 else:
2347 if is_sha1:
2348 branch = self.upstream
2349 if branch is not None and branch.strip():
2350 if not branch.startswith('refs/'):
2351 branch = R_HEADS + branch
2352 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2353
2354 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2355 # whole repo.
2356 if self.manifest.IsMirror and not spec:
2357 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2358
2359 # If using depth then we should not get all the tags since they may
2360 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002361 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002362 cmd.append('--no-tags')
2363 else:
2364 cmd.append('--tags')
2365 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2366
Conley Owens80b87fe2014-05-09 17:13:44 -07002367 cmd.extend(spec)
2368
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002369 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002370 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002371 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2372 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002373 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002374 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002375 ok = True
2376 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002377 # If needed, run the 'git remote prune' the first time through the loop
2378 elif (not _i and
2379 "error:" in gitcmd.stderr and
2380 "git remote prune" in gitcmd.stderr):
2381 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002382 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002383 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002384 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002385 break
2386 continue
Brian Harring14a66742012-09-28 20:21:57 -07002387 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002388 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2389 # in sha1 mode, we just tried sync'ing from the upstream field; it
2390 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002391 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002392 elif ret < 0:
2393 # Git died with a signal, exit immediately
2394 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002395 if not verbose:
2396 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002397 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002398
2399 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002400 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002401 if old_packed != '':
2402 _lwrite(packed_refs, old_packed)
2403 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002404 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002405 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002406
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002407 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002408 # We just synced the upstream given branch; verify we
2409 # got what we wanted, else trigger a second run of all
2410 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002411 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002412 # Sync the current branch only with depth set to None.
2413 # We always pass depth=None down to avoid infinite recursion.
2414 return self._RemoteFetch(
2415 name=name, quiet=quiet, verbose=verbose,
2416 current_branch_only=current_branch_only and depth,
2417 initial=False, alt_dir=alt_dir,
2418 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002419
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002420 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002421
Mike Frysingere50b6a72020-02-19 01:45:48 -05002422 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002423 if initial and \
2424 (self.manifest.manifestProject.config.GetString('repo.depth') or
2425 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002426 return False
2427
2428 remote = self.GetRemote(self.remote.name)
2429 bundle_url = remote.url + '/clone.bundle'
2430 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002431 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2432 'persistent-http',
2433 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002434 return False
2435
2436 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2437 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2438
2439 exist_dst = os.path.exists(bundle_dst)
2440 exist_tmp = os.path.exists(bundle_tmp)
2441
2442 if not initial and not exist_dst and not exist_tmp:
2443 return False
2444
2445 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002446 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2447 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002448 if not exist_dst:
2449 return False
2450
2451 cmd = ['fetch']
2452 if quiet:
2453 cmd.append('--quiet')
2454 if not self.worktree:
2455 cmd.append('--update-head-ok')
2456 cmd.append(bundle_dst)
2457 for f in remote.fetch:
2458 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002459 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002460
2461 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002462 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002463 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002464 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002465 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002466 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467
Mike Frysingere50b6a72020-02-19 01:45:48 -05002468 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002469 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002470 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002471
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002472 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002473 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002474 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002475 if os.path.exists(tmpPath):
2476 size = os.stat(tmpPath).st_size
2477 if size >= 1024:
2478 cmd += ['--continue-at', '%d' % (size,)]
2479 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002480 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002481 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002482 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002483 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002484 if proxy:
2485 cmd += ['--proxy', proxy]
2486 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2487 cmd += ['--proxy', os.environ['http_proxy']]
2488 if srcUrl.startswith('persistent-https'):
2489 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2490 elif srcUrl.startswith('persistent-http'):
2491 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002492 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002493
Dave Borowitz137d0132015-01-02 11:12:54 -08002494 if IsTrace():
2495 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002496 if verbose:
2497 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2498 stdout = None if verbose else subprocess.PIPE
2499 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002500 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002501 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002502 except OSError:
2503 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002504
Mike Frysingere50b6a72020-02-19 01:45:48 -05002505 (output, _) = proc.communicate()
2506 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002507
Dave Borowitz137d0132015-01-02 11:12:54 -08002508 if curlret == 22:
2509 # From curl man page:
2510 # 22: HTTP page not retrieved. The requested url was not found or
2511 # returned another error with the HTTP error code being 400 or above.
2512 # This return code only appears if -f, --fail is used.
2513 if not quiet:
2514 print("Server does not provide clone.bundle; ignoring.",
2515 file=sys.stderr)
2516 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002517 elif curlret and not verbose and output:
2518 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002519
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002520 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002521 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002522 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002523 return True
2524 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002525 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002526 return False
2527 else:
2528 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002529
Kris Giesingc8d882a2014-12-23 13:02:32 -08002530 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002531 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002532 with open(path, 'rb') as f:
2533 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002534 return True
2535 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002536 if not quiet:
2537 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002538 return False
2539 except OSError:
2540 return False
2541
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002542 def _Checkout(self, rev, quiet=False):
2543 cmd = ['checkout']
2544 if quiet:
2545 cmd.append('-q')
2546 cmd.append(rev)
2547 cmd.append('--')
2548 if GitCommand(self, cmd).Wait() != 0:
2549 if self._allrefs:
2550 raise GitError('%s checkout %s ' % (self.name, rev))
2551
Anthony King7bdac712014-07-16 12:56:40 +01002552 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002553 cmd = ['cherry-pick']
2554 cmd.append(rev)
2555 cmd.append('--')
2556 if GitCommand(self, cmd).Wait() != 0:
2557 if self._allrefs:
2558 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2559
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302560 def _LsRemote(self, refs):
2561 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302562 p = GitCommand(self, cmd, capture_stdout=True)
2563 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002564 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302565 return None
2566
Anthony King7bdac712014-07-16 12:56:40 +01002567 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002568 cmd = ['revert']
2569 cmd.append('--no-edit')
2570 cmd.append(rev)
2571 cmd.append('--')
2572 if GitCommand(self, cmd).Wait() != 0:
2573 if self._allrefs:
2574 raise GitError('%s revert %s ' % (self.name, rev))
2575
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002576 def _ResetHard(self, rev, quiet=True):
2577 cmd = ['reset', '--hard']
2578 if quiet:
2579 cmd.append('-q')
2580 cmd.append(rev)
2581 if GitCommand(self, cmd).Wait() != 0:
2582 raise GitError('%s reset --hard %s ' % (self.name, rev))
2583
Martin Kellye4e94d22017-03-21 16:05:12 -07002584 def _SyncSubmodules(self, quiet=True):
2585 cmd = ['submodule', 'update', '--init', '--recursive']
2586 if quiet:
2587 cmd.append('-q')
2588 if GitCommand(self, cmd).Wait() != 0:
2589 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2590
Anthony King7bdac712014-07-16 12:56:40 +01002591 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002592 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002593 if onto is not None:
2594 cmd.extend(['--onto', onto])
2595 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002596 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597 raise GitError('%s rebase %s ' % (self.name, upstream))
2598
Pierre Tardy3d125942012-05-04 12:18:12 +02002599 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002600 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002601 if ffonly:
2602 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002603 if GitCommand(self, cmd).Wait() != 0:
2604 raise GitError('%s merge %s ' % (self.name, head))
2605
David Pursehousee8ace262020-02-13 12:41:15 +09002606 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002607 init_git_dir = not os.path.exists(self.gitdir)
2608 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002609 try:
2610 # Initialize the bare repository, which contains all of the objects.
2611 if init_obj_dir:
2612 os.makedirs(self.objdir)
2613 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002614
Kevin Degib1a07b82015-07-27 13:33:43 -06002615 # If we have a separate directory to hold refs, initialize it as well.
2616 if self.objdir != self.gitdir:
2617 if init_git_dir:
2618 os.makedirs(self.gitdir)
2619
2620 if init_obj_dir or init_git_dir:
2621 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2622 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002623 try:
2624 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2625 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002626 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002627 print("Retrying clone after deleting %s" %
2628 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002629 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002630 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2631 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002632 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002633 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002634 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2635 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002636 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002637 raise e
2638 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002639
Kevin Degi384b3c52014-10-16 16:02:58 -06002640 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002641 mp = self.manifest.manifestProject
2642 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002643
Kevin Degib1a07b82015-07-27 13:33:43 -06002644 if ref_dir or mirror_git:
2645 if not mirror_git:
2646 mirror_git = os.path.join(ref_dir, self.name + '.git')
2647 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2648 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002649
Kevin Degib1a07b82015-07-27 13:33:43 -06002650 if os.path.exists(mirror_git):
2651 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002652
Kevin Degib1a07b82015-07-27 13:33:43 -06002653 elif os.path.exists(repo_git):
2654 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002655
Kevin Degib1a07b82015-07-27 13:33:43 -06002656 else:
2657 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002658
Kevin Degib1a07b82015-07-27 13:33:43 -06002659 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002660 if not os.path.isabs(ref_dir):
2661 # The alternate directory is relative to the object database.
2662 ref_dir = os.path.relpath(ref_dir,
2663 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002664 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2665 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002666
David Pursehousee8ace262020-02-13 12:41:15 +09002667 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002668
2669 m = self.manifest.manifestProject.config
2670 for key in ['user.name', 'user.email']:
2671 if m.Has(key, include_defaults=False):
2672 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002673 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002674 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002675 if self.manifest.IsMirror:
2676 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002677 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002678 self.config.SetString('core.bare', None)
2679 except Exception:
2680 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002681 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002682 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002683 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002684 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002685
David Pursehousee8ace262020-02-13 12:41:15 +09002686 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002687 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002688 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689
David Pursehousee8ace262020-02-13 12:41:15 +09002690 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002691 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002692 if not os.path.exists(hooks):
2693 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002694 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002695 name = os.path.basename(stock_hook)
2696
Victor Boivie65e0f352011-04-18 11:23:29 +02002697 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002698 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002699 # Don't install a Gerrit Code Review hook if this
2700 # project does not appear to use it for reviews.
2701 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002702 # Since the manifest project is one of those, but also
2703 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002704 continue
2705
2706 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002707 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002708 continue
2709 if os.path.exists(dst):
2710 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002711 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002712 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002713 if not quiet:
2714 _warn("%s: Not replacing locally modified %s hook",
2715 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002716 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002717 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002718 platform_utils.symlink(
2719 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002720 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002721 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002722 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002723 else:
2724 raise
2725
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002726 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002727 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002728 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002729 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002730 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002731 remote.review = self.remote.review
2732 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002733
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002734 if self.worktree:
2735 remote.ResetFetch(mirror=False)
2736 else:
2737 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738 remote.Save()
2739
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002740 def _InitMRef(self):
2741 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002742 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002743
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002744 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002745 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002746
2747 def _InitAnyMRef(self, ref):
2748 cur = self.bare_ref.symref(ref)
2749
2750 if self.revisionId:
2751 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2752 msg = 'manifest set to %s' % self.revisionId
2753 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002754 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002755 else:
2756 remote = self.GetRemote(self.remote.name)
2757 dst = remote.ToLocal(self.revisionExpr)
2758 if cur != dst:
2759 msg = 'manifest set to %s' % self.revisionExpr
2760 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002761
Kevin Degi384b3c52014-10-16 16:02:58 -06002762 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002763 symlink_files = self.shareable_files[:]
2764 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002765 if share_refs:
2766 symlink_files += self.working_tree_files
2767 symlink_dirs += self.working_tree_dirs
2768 to_symlink = symlink_files + symlink_dirs
2769 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002770 # Try to self-heal a bit in simple cases.
2771 dst_path = os.path.join(destdir, name)
2772 src_path = os.path.join(srcdir, name)
2773
2774 if name in self.working_tree_dirs:
2775 # If the dir is missing under .repo/projects/, create it.
2776 if not os.path.exists(src_path):
2777 os.makedirs(src_path)
2778
2779 elif name in self.working_tree_files:
2780 # If it's a file under the checkout .git/ and the .repo/projects/ has
2781 # nothing, move the file under the .repo/projects/ tree.
2782 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2783 platform_utils.rename(dst_path, src_path)
2784
2785 # If the path exists under the .repo/projects/ and there's no symlink
2786 # under the checkout .git/, recreate the symlink.
2787 if name in self.working_tree_dirs or name in self.working_tree_files:
2788 if os.path.exists(src_path) and not os.path.exists(dst_path):
2789 platform_utils.symlink(
2790 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2791
2792 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002793 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002794 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002795 # Fail if the links are pointing to the wrong place
2796 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002797 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002798 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002799 'work tree. If you\'re comfortable with the '
2800 'possibility of losing the work tree\'s git metadata,'
2801 ' use `repo sync --force-sync {0}` to '
2802 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002803
David James8d201162013-10-11 17:03:19 -07002804 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2805 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2806
2807 Args:
2808 gitdir: The bare git repository. Must already be initialized.
2809 dotgit: The repository you would like to initialize.
2810 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2811 Only one work tree can store refs under a given |gitdir|.
2812 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2813 This saves you the effort of initializing |dotgit| yourself.
2814 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002815 symlink_files = self.shareable_files[:]
2816 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002817 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002818 symlink_files += self.working_tree_files
2819 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002820 to_symlink = symlink_files + symlink_dirs
2821
2822 to_copy = []
2823 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002824 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002825
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002826 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002827 for name in set(to_copy).union(to_symlink):
2828 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002829 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002830 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002831
Kevin Degi384b3c52014-10-16 16:02:58 -06002832 if os.path.lexists(dst):
2833 continue
David James8d201162013-10-11 17:03:19 -07002834
2835 # If the source dir doesn't exist, create an empty dir.
2836 if name in symlink_dirs and not os.path.lexists(src):
2837 os.makedirs(src)
2838
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002839 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002840 platform_utils.symlink(
2841 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002842 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002843 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002844 shutil.copytree(src, dst)
2845 elif os.path.isfile(src):
2846 shutil.copy(src, dst)
2847
Conley Owens80b87fe2014-05-09 17:13:44 -07002848 # If the source file doesn't exist, ensure the destination
2849 # file doesn't either.
2850 if name in symlink_files and not os.path.lexists(src):
2851 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002852 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002853 except OSError:
2854 pass
2855
David James8d201162013-10-11 17:03:19 -07002856 except OSError as e:
2857 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002858 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002859 else:
2860 raise
2861
Martin Kellye4e94d22017-03-21 16:05:12 -07002862 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002863 realdotgit = os.path.join(self.worktree, '.git')
2864 tmpdotgit = realdotgit + '.tmp'
2865 init_dotgit = not os.path.exists(realdotgit)
2866 if init_dotgit:
2867 dotgit = tmpdotgit
2868 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2869 os.makedirs(tmpdotgit)
2870 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2871 copy_all=False)
2872 else:
2873 dotgit = realdotgit
2874
Kevin Degib1a07b82015-07-27 13:33:43 -06002875 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002876 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2877 except GitError as e:
2878 if force_sync and not init_dotgit:
2879 try:
2880 platform_utils.rmtree(dotgit)
2881 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002882 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002883 raise e
2884 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002885
Mike Frysingerf4545122019-11-11 04:34:16 -05002886 if init_dotgit:
2887 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002888
Mike Frysingerf4545122019-11-11 04:34:16 -05002889 # Now that the .git dir is fully set up, move it to its final home.
2890 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002891
Mike Frysingerf4545122019-11-11 04:34:16 -05002892 # Finish checking out the worktree.
2893 cmd = ['read-tree', '--reset', '-u']
2894 cmd.append('-v')
2895 cmd.append(HEAD)
2896 if GitCommand(self, cmd).Wait() != 0:
2897 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002898
Mike Frysingerf4545122019-11-11 04:34:16 -05002899 if submodules:
2900 self._SyncSubmodules(quiet=True)
2901 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002902
Renaud Paquay788e9622017-01-27 11:41:12 -08002903 def _get_symlink_error_message(self):
2904 if platform_utils.isWindows():
2905 return ('Unable to create symbolic link. Please re-run the command as '
2906 'Administrator, or see '
2907 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2908 'for other options.')
2909 return 'filesystem must support symlinks'
2910
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002911 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002912 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002913
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002914 def _revlist(self, *args, **kw):
2915 a = []
2916 a.extend(args)
2917 a.append('--')
2918 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002919
2920 @property
2921 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002922 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002923
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002924 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002925 """Get logs between two revisions of this project."""
2926 comp = '..'
2927 if rev1:
2928 revs = [rev1]
2929 if rev2:
2930 revs.extend([comp, rev2])
2931 cmd = ['log', ''.join(revs)]
2932 out = DiffColoring(self.config)
2933 if out.is_on and color:
2934 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002935 if pretty_format is not None:
2936 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002937 if oneline:
2938 cmd.append('--oneline')
2939
2940 try:
2941 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2942 if log.Wait() == 0:
2943 return log.stdout
2944 except GitError:
2945 # worktree may not exist if groups changed for example. In that case,
2946 # try in gitdir instead.
2947 if not os.path.exists(self.worktree):
2948 return self.bare_git.log(*cmd[1:])
2949 else:
2950 raise
2951 return None
2952
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002953 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2954 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002955 """Get the list of logs from this revision to given revisionId"""
2956 logs = {}
2957 selfId = self.GetRevisionId(self._allrefs)
2958 toId = toProject.GetRevisionId(toProject._allrefs)
2959
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002960 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2961 pretty_format=pretty_format)
2962 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2963 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002964 return logs
2965
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002967
David James8d201162013-10-11 17:03:19 -07002968 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002969 self._project = project
2970 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002971 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002972
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002973 def LsOthers(self):
2974 p = GitCommand(self._project,
2975 ['ls-files',
2976 '-z',
2977 '--others',
2978 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002979 bare=False,
David James8d201162013-10-11 17:03:19 -07002980 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002981 capture_stdout=True,
2982 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002983 if p.Wait() == 0:
2984 out = p.stdout
2985 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002986 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002987 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 return []
2989
2990 def DiffZ(self, name, *args):
2991 cmd = [name]
2992 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002993 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002994 cmd.extend(args)
2995 p = GitCommand(self._project,
2996 cmd,
David James8d201162013-10-11 17:03:19 -07002997 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002998 bare=False,
2999 capture_stdout=True,
3000 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003001 try:
3002 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003003 if not hasattr(out, 'encode'):
3004 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005 r = {}
3006 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003007 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003008 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003009 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003010 info = next(out)
3011 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003012 except StopIteration:
3013 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003014
3015 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003016
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003017 def __init__(self, path, omode, nmode, oid, nid, state):
3018 self.path = path
3019 self.src_path = None
3020 self.old_mode = omode
3021 self.new_mode = nmode
3022 self.old_id = oid
3023 self.new_id = nid
3024
3025 if len(state) == 1:
3026 self.status = state
3027 self.level = None
3028 else:
3029 self.status = state[:1]
3030 self.level = state[1:]
3031 while self.level.startswith('0'):
3032 self.level = self.level[1:]
3033
3034 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003035 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003036 if info.status in ('R', 'C'):
3037 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003038 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003039 r[info.path] = info
3040 return r
3041 finally:
3042 p.Wait()
3043
3044 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003045 if self._bare:
3046 path = os.path.join(self._project.gitdir, HEAD)
3047 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003048 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003049 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003050 with open(path) as fd:
3051 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003052 except IOError as e:
3053 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003054 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303055 line = line.decode()
3056 except AttributeError:
3057 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003058 if line.startswith('ref: '):
3059 return line[5:-1]
3060 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003061
3062 def SetHead(self, ref, message=None):
3063 cmdv = []
3064 if message is not None:
3065 cmdv.extend(['-m', message])
3066 cmdv.append(HEAD)
3067 cmdv.append(ref)
3068 self.symbolic_ref(*cmdv)
3069
3070 def DetachHead(self, new, message=None):
3071 cmdv = ['--no-deref']
3072 if message is not None:
3073 cmdv.extend(['-m', message])
3074 cmdv.append(HEAD)
3075 cmdv.append(new)
3076 self.update_ref(*cmdv)
3077
3078 def UpdateRef(self, name, new, old=None,
3079 message=None,
3080 detach=False):
3081 cmdv = []
3082 if message is not None:
3083 cmdv.extend(['-m', message])
3084 if detach:
3085 cmdv.append('--no-deref')
3086 cmdv.append(name)
3087 cmdv.append(new)
3088 if old is not None:
3089 cmdv.append(old)
3090 self.update_ref(*cmdv)
3091
3092 def DeleteRef(self, name, old=None):
3093 if not old:
3094 old = self.rev_parse(name)
3095 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003096 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003097
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003098 def rev_list(self, *args, **kw):
3099 if 'format' in kw:
3100 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3101 else:
3102 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003103 cmdv.extend(args)
3104 p = GitCommand(self._project,
3105 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003106 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003107 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003108 capture_stdout=True,
3109 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003110 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003111 raise GitError('%s rev-list %s: %s' %
3112 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003113 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003114
3115 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003116 """Allow arbitrary git commands using pythonic syntax.
3117
3118 This allows you to do things like:
3119 git_obj.rev_parse('HEAD')
3120
3121 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3122 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003123 Any other positional arguments will be passed to the git command, and the
3124 following keyword arguments are supported:
3125 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003126
3127 Args:
3128 name: The name of the git command to call. Any '_' characters will
3129 be replaced with '-'.
3130
3131 Returns:
3132 A callable object that will try to call git with the named command.
3133 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003134 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003135
Dave Borowitz091f8932012-10-23 17:01:04 -07003136 def runner(*args, **kwargs):
3137 cmdv = []
3138 config = kwargs.pop('config', None)
3139 for k in kwargs:
3140 raise TypeError('%s() got an unexpected keyword argument %r'
3141 % (name, k))
3142 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303143 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003144 cmdv.append('-c')
3145 cmdv.append('%s=%s' % (k, v))
3146 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003147 cmdv.extend(args)
3148 p = GitCommand(self._project,
3149 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003150 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003151 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003152 capture_stdout=True,
3153 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003154 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003155 raise GitError('%s %s: %s' %
3156 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003157 r = p.stdout
3158 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3159 return r[:-1]
3160 return r
3161 return runner
3162
3163
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003164class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003165
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003166 def __str__(self):
3167 return 'prior sync failed; rebase still in progress'
3168
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003169
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003170class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003171
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003172 def __str__(self):
3173 return 'contains uncommitted changes'
3174
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003176class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003177
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003178 def __init__(self, project, text):
3179 self.project = project
3180 self.text = text
3181
3182 def Print(self, syncbuf):
3183 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3184 syncbuf.out.nl()
3185
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003186
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003187class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003189 def __init__(self, project, why):
3190 self.project = project
3191 self.why = why
3192
3193 def Print(self, syncbuf):
3194 syncbuf.out.fail('error: %s/: %s',
3195 self.project.relpath,
3196 str(self.why))
3197 syncbuf.out.nl()
3198
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003199
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003200class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003201
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003202 def __init__(self, project, action):
3203 self.project = project
3204 self.action = action
3205
3206 def Run(self, syncbuf):
3207 out = syncbuf.out
3208 out.project('project %s/', self.project.relpath)
3209 out.nl()
3210 try:
3211 self.action()
3212 out.nl()
3213 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003214 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215 out.nl()
3216 return False
3217
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003219class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003220
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003221 def __init__(self, config):
3222 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003223 self.project = self.printer('header', attr='bold')
3224 self.info = self.printer('info')
3225 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003226
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003227
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003228class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003229
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230 def __init__(self, config, detach_head=False):
3231 self._messages = []
3232 self._failures = []
3233 self._later_queue1 = []
3234 self._later_queue2 = []
3235
3236 self.out = _SyncColoring(config)
3237 self.out.redirect(sys.stderr)
3238
3239 self.detach_head = detach_head
3240 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003241 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003242
3243 def info(self, project, fmt, *args):
3244 self._messages.append(_InfoMessage(project, fmt % args))
3245
3246 def fail(self, project, err=None):
3247 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003248 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003249
3250 def later1(self, project, what):
3251 self._later_queue1.append(_Later(project, what))
3252
3253 def later2(self, project, what):
3254 self._later_queue2.append(_Later(project, what))
3255
3256 def Finish(self):
3257 self._PrintMessages()
3258 self._RunLater()
3259 self._PrintMessages()
3260 return self.clean
3261
David Rileye0684ad2017-04-05 00:02:59 -07003262 def Recently(self):
3263 recent_clean = self.recent_clean
3264 self.recent_clean = True
3265 return recent_clean
3266
3267 def _MarkUnclean(self):
3268 self.clean = False
3269 self.recent_clean = False
3270
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003271 def _RunLater(self):
3272 for q in ['_later_queue1', '_later_queue2']:
3273 if not self._RunQueue(q):
3274 return
3275
3276 def _RunQueue(self, queue):
3277 for m in getattr(self, queue):
3278 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003279 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003280 return False
3281 setattr(self, queue, [])
3282 return True
3283
3284 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003285 if self._messages or self._failures:
3286 if os.isatty(2):
3287 self.out.write(progress.CSI_ERASE_LINE)
3288 self.out.write('\r')
3289
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003290 for m in self._messages:
3291 m.Print(self)
3292 for m in self._failures:
3293 m.Print(self)
3294
3295 self._messages = []
3296 self._failures = []
3297
3298
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003299class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003300
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003301 """A special project housed under .repo.
3302 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003303
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003304 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003305 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003306 manifest=manifest,
3307 name=name,
3308 gitdir=gitdir,
3309 objdir=gitdir,
3310 worktree=worktree,
3311 remote=RemoteSpec('origin'),
3312 relpath='.repo/%s' % name,
3313 revisionExpr='refs/heads/master',
3314 revisionId=None,
3315 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003316
3317 def PreSync(self):
3318 if self.Exists:
3319 cb = self.CurrentBranch
3320 if cb:
3321 base = self.GetBranch(cb).merge
3322 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003323 self.revisionExpr = base
3324 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003325
Martin Kelly224a31a2017-07-10 14:46:25 -07003326 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003327 """ Prepare MetaProject for manifest branch switch
3328 """
3329
3330 # detach and delete manifest branch, allowing a new
3331 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003332 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003333 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003334 syncbuf.Finish()
3335
3336 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003337 ['update-ref', '-d', 'refs/heads/default'],
3338 capture_stdout=True,
3339 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003340
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003341 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003342 def LastFetch(self):
3343 try:
3344 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3345 return os.path.getmtime(fh)
3346 except OSError:
3347 return 0
3348
3349 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003350 def HasChanges(self):
3351 """Has the remote received new commits not yet checked out?
3352 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003353 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003354 return False
3355
David Pursehouse8a68ff92012-09-24 12:15:13 +09003356 all_refs = self.bare_ref.all
3357 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003358 head = self.work_git.GetHead()
3359 if head.startswith(R_HEADS):
3360 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003361 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003362 except KeyError:
3363 head = None
3364
3365 if revid == head:
3366 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003367 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003368 return True
3369 return False