blob: 86c9ef007de3c7525fe12d727c5f1181fb3ec675 [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,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000204 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000216 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
242 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
256 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Mike Frysingere6a202f2019-08-02 15:57:57 -0400269def _SafeExpandPath(base, subpath, skipfinal=False):
270 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700271
Mike Frysingere6a202f2019-08-02 15:57:57 -0400272 We make sure no intermediate symlinks are traversed, and that the final path
273 is not a special file (e.g. not a socket or fifo).
274
275 NB: We rely on a number of paths already being filtered out while parsing the
276 manifest. See the validation logic in manifest_xml.py for more details.
277 """
278 components = subpath.split(os.path.sep)
279 if skipfinal:
280 # Whether the caller handles the final component itself.
281 finalpart = components.pop()
282
283 path = base
284 for part in components:
285 if part in {'.', '..'}:
286 raise ManifestInvalidPathError(
287 '%s: "%s" not allowed in paths' % (subpath, part))
288
289 path = os.path.join(path, part)
290 if platform_utils.islink(path):
291 raise ManifestInvalidPathError(
292 '%s: traversing symlinks not allow' % (path,))
293
294 if os.path.exists(path):
295 if not os.path.isfile(path) and not platform_utils.isdir(path):
296 raise ManifestInvalidPathError(
297 '%s: only regular files & directories allowed' % (path,))
298
299 if skipfinal:
300 path = os.path.join(path, finalpart)
301
302 return path
303
304
305class _CopyFile(object):
306 """Container for <copyfile> manifest element."""
307
308 def __init__(self, git_worktree, src, topdir, dest):
309 """Register a <copyfile> request.
310
311 Args:
312 git_worktree: Absolute path to the git project checkout.
313 src: Relative path under |git_worktree| of file to read.
314 topdir: Absolute path to the top of the repo client checkout.
315 dest: Relative path under |topdir| of file to write.
316 """
317 self.git_worktree = git_worktree
318 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700319 self.src = src
320 self.dest = dest
321
322 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400323 src = _SafeExpandPath(self.git_worktree, self.src)
324 dest = _SafeExpandPath(self.topdir, self.dest)
325
326 if platform_utils.isdir(src):
327 raise ManifestInvalidPathError(
328 '%s: copying from directory not supported' % (self.src,))
329 if platform_utils.isdir(dest):
330 raise ManifestInvalidPathError(
331 '%s: copying to directory not allowed' % (self.dest,))
332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333 # copy file if it does not exist or is out of date
334 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
335 try:
336 # remove existing file first, since it might be read-only
337 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800338 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400339 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700341 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200342 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 shutil.copy(src, dest)
344 # make the file read-only
345 mode = os.stat(dest)[stat.ST_MODE]
346 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
347 os.chmod(dest, mode)
348 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700349 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700351
Anthony King7bdac712014-07-16 12:56:40 +0100352class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700354
Mike Frysingere6a202f2019-08-02 15:57:57 -0400355 def __init__(self, git_worktree, src, topdir, dest):
356 """Register a <linkfile> request.
357
358 Args:
359 git_worktree: Absolute path to the git project checkout.
360 src: Target of symlink relative to path under |git_worktree|.
361 topdir: Absolute path to the top of the repo client checkout.
362 dest: Relative path under |topdir| of symlink to create.
363 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700364 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400365 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366 self.src = src
367 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368
Wink Saville4c426ef2015-06-03 08:05:17 -0700369 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700371 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500372 try:
373 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800374 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800375 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500376 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700377 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700378 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700380 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700382 _error('Cannot link file %s to %s', relSrc, absDest)
383
384 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400385 """Link the self.src & self.dest paths.
386
387 Handles wild cards on the src linking all of the files in the source in to
388 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700389 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500390 # Some people use src="." to create stable links to projects. Lets allow
391 # that but reject all other uses of "." to keep things simple.
392 if self.src == '.':
393 src = self.git_worktree
394 else:
395 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400396
397 if os.path.exists(src):
398 # Entity exists so just a simple one to one link operation.
399 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
400 # dest & src are absolute paths at this point. Make sure the target of
401 # the symlink is relative in the context of the repo client checkout.
402 relpath = os.path.relpath(src, os.path.dirname(dest))
403 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700406 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400407 if os.path.exists(dest) and not platform_utils.isdir(dest):
408 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 # Create a releative path from source dir to destination dir
412 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400413 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700414
415 # Get the source file name
416 srcFile = os.path.basename(absSrcFile)
417
418 # Now form the final full paths to srcFile. They will be
419 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700421 relSrc = os.path.join(relSrcDir, srcFile)
422 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500423
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700426
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700427 def __init__(self,
428 name,
Anthony King7bdac712014-07-16 12:56:40 +0100429 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700430 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100431 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700432 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700433 orig_name=None,
434 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700435 self.name = name
436 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700437 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700438 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100439 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700440 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700441 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700443
Doug Anderson37282b42011-03-04 11:54:18 -0800444class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700445
Doug Anderson37282b42011-03-04 11:54:18 -0800446 """A RepoHook contains information about a script to run as a hook.
447
448 Hooks are used to run a python script before running an upload (for instance,
449 to run presubmit checks). Eventually, we may have hooks for other actions.
450
451 This shouldn't be confused with files in the 'repo/hooks' directory. Those
452 files are copied into each '.git/hooks' folder for each project. Repo-level
453 hooks are associated instead with repo actions.
454
455 Hooks are always python. When a hook is run, we will load the hook into the
456 interpreter and execute its main() function.
457 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700458
Doug Anderson37282b42011-03-04 11:54:18 -0800459 def __init__(self,
460 hook_type,
461 hooks_project,
462 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400463 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800464 abort_if_user_denies=False):
465 """RepoHook constructor.
466
467 Params:
468 hook_type: A string representing the type of hook. This is also used
469 to figure out the name of the file containing the hook. For
470 example: 'pre-upload'.
471 hooks_project: The project containing the repo hooks. If you have a
472 manifest, this is manifest.repo_hooks_project. OK if this is None,
473 which will make the hook a no-op.
474 topdir: Repo's top directory (the one containing the .repo directory).
475 Scripts will run with CWD as this directory. If you have a manifest,
476 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800478 abort_if_user_denies: If True, we'll throw a HookError() if the user
479 doesn't allow us to run the hook.
480 """
481 self._hook_type = hook_type
482 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800484 self._topdir = topdir
485 self._abort_if_user_denies = abort_if_user_denies
486
487 # Store the full path to the script for convenience.
488 if self._hooks_project:
489 self._script_fullpath = os.path.join(self._hooks_project.worktree,
490 self._hook_type + '.py')
491 else:
492 self._script_fullpath = None
493
494 def _GetHash(self):
495 """Return a hash of the contents of the hooks directory.
496
497 We'll just use git to do this. This hash has the property that if anything
498 changes in the directory we will return a different has.
499
500 SECURITY CONSIDERATION:
501 This hash only represents the contents of files in the hook directory, not
502 any other files imported or called by hooks. Changes to imported files
503 can change the script behavior without affecting the hash.
504
505 Returns:
506 A string representing the hash. This will always be ASCII so that it can
507 be printed to the user easily.
508 """
509 assert self._hooks_project, "Must have hooks to calculate their hash."
510
511 # We will use the work_git object rather than just calling GetRevisionId().
512 # That gives us a hash of the latest checked in version of the files that
513 # the user will actually be executing. Specifically, GetRevisionId()
514 # doesn't appear to change even if a user checks out a different version
515 # of the hooks repo (via git checkout) nor if a user commits their own revs.
516 #
517 # NOTE: Local (non-committed) changes will not be factored into this hash.
518 # I think this is OK, since we're really only worried about warning the user
519 # about upstream changes.
520 return self._hooks_project.work_git.rev_parse('HEAD')
521
522 def _GetMustVerb(self):
523 """Return 'must' if the hook is required; 'should' if not."""
524 if self._abort_if_user_denies:
525 return 'must'
526 else:
527 return 'should'
528
529 def _CheckForHookApproval(self):
530 """Check to see whether this hook has been approved.
531
Mike Frysinger40252c22016-08-15 21:23:44 -0400532 We'll accept approval of manifest URLs if they're using secure transports.
533 This way the user can say they trust the manifest hoster. For insecure
534 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800535
536 Note that we ask permission for each individual hook even though we use
537 the hash of all hooks when detecting changes. We'd like the user to be
538 able to approve / deny each hook individually. We only use the hash of all
539 hooks because there is no other easy way to detect changes to local imports.
540
541 Returns:
542 True if this hook is approved to run; False otherwise.
543
544 Raises:
545 HookError: Raised if the user doesn't approve and abort_if_user_denies
546 was passed to the consturctor.
547 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400548 if self._ManifestUrlHasSecureScheme():
549 return self._CheckForHookApprovalManifest()
550 else:
551 return self._CheckForHookApprovalHash()
552
553 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
554 changed_prompt):
555 """Check for approval for a particular attribute and hook.
556
557 Args:
558 subkey: The git config key under [repo.hooks.<hook_type>] to store the
559 last approved string.
560 new_val: The new value to compare against the last approved one.
561 main_prompt: Message to display to the user to ask for approval.
562 changed_prompt: Message explaining why we're re-asking for approval.
563
564 Returns:
565 True if this hook is approved to run; False otherwise.
566
567 Raises:
568 HookError: Raised if the user doesn't approve and abort_if_user_denies
569 was passed to the consturctor.
570 """
Doug Anderson37282b42011-03-04 11:54:18 -0800571 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400572 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800573
Mike Frysinger40252c22016-08-15 21:23:44 -0400574 # Get the last value that the user approved for this hook; may be None.
575 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800576
Mike Frysinger40252c22016-08-15 21:23:44 -0400577 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800578 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400579 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800580 # Approval matched. We're done.
581 return True
582 else:
583 # Give the user a reason why we're prompting, since they last told
584 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400585 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800586 else:
587 prompt = ''
588
589 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
590 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400591 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530592 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900593 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800594
595 # User is doing a one-time approval.
596 if response in ('y', 'yes'):
597 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400598 elif response == 'always':
599 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800600 return True
601
602 # For anything else, we'll assume no approval.
603 if self._abort_if_user_denies:
604 raise HookError('You must allow the %s hook or use --no-verify.' %
605 self._hook_type)
606
607 return False
608
Mike Frysinger40252c22016-08-15 21:23:44 -0400609 def _ManifestUrlHasSecureScheme(self):
610 """Check if the URI for the manifest is a secure transport."""
611 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
612 parse_results = urllib.parse.urlparse(self._manifest_url)
613 return parse_results.scheme in secure_schemes
614
615 def _CheckForHookApprovalManifest(self):
616 """Check whether the user has approved this manifest host.
617
618 Returns:
619 True if this hook is approved to run; False otherwise.
620 """
621 return self._CheckForHookApprovalHelper(
622 'approvedmanifest',
623 self._manifest_url,
624 'Run hook scripts from %s' % (self._manifest_url,),
625 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
626
627 def _CheckForHookApprovalHash(self):
628 """Check whether the user has approved the hooks repo.
629
630 Returns:
631 True if this hook is approved to run; False otherwise.
632 """
633 prompt = ('Repo %s run the script:\n'
634 ' %s\n'
635 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700636 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400637 return self._CheckForHookApprovalHelper(
638 'approvedhash',
639 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700640 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400641 'Scripts have changed since %s was allowed.' % (self._hook_type,))
642
Mike Frysingerf7c51602019-06-18 17:23:39 -0400643 @staticmethod
644 def _ExtractInterpFromShebang(data):
645 """Extract the interpreter used in the shebang.
646
647 Try to locate the interpreter the script is using (ignoring `env`).
648
649 Args:
650 data: The file content of the script.
651
652 Returns:
653 The basename of the main script interpreter, or None if a shebang is not
654 used or could not be parsed out.
655 """
656 firstline = data.splitlines()[:1]
657 if not firstline:
658 return None
659
660 # The format here can be tricky.
661 shebang = firstline[0].strip()
662 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
663 if not m:
664 return None
665
666 # If the using `env`, find the target program.
667 interp = m.group(1)
668 if os.path.basename(interp) == 'env':
669 interp = m.group(2)
670
671 return interp
672
673 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
674 """Execute the hook script through |interp|.
675
676 Note: Support for this feature should be dropped ~Jun 2021.
677
678 Args:
679 interp: The Python program to run.
680 context: Basic Python context to execute the hook inside.
681 kwargs: Arbitrary arguments to pass to the hook script.
682
683 Raises:
684 HookError: When the hooks failed for any reason.
685 """
686 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
687 script = """
688import json, os, sys
689path = '''%(path)s'''
690kwargs = json.loads('''%(kwargs)s''')
691context = json.loads('''%(context)s''')
692sys.path.insert(0, os.path.dirname(path))
693data = open(path).read()
694exec(compile(data, path, 'exec'), context)
695context['main'](**kwargs)
696""" % {
697 'path': self._script_fullpath,
698 'kwargs': json.dumps(kwargs),
699 'context': json.dumps(context),
700 }
701
702 # We pass the script via stdin to avoid OS argv limits. It also makes
703 # unhandled exception tracebacks less verbose/confusing for users.
704 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
705 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
706 proc.communicate(input=script.encode('utf-8'))
707 if proc.returncode:
708 raise HookError('Failed to run %s hook.' % (self._hook_type,))
709
710 def _ExecuteHookViaImport(self, data, context, **kwargs):
711 """Execute the hook code in |data| directly.
712
713 Args:
714 data: The code of the hook to execute.
715 context: Basic Python context to execute the hook inside.
716 kwargs: Arbitrary arguments to pass to the hook script.
717
718 Raises:
719 HookError: When the hooks failed for any reason.
720 """
721 # Exec, storing global context in the context dict. We catch exceptions
722 # and convert to a HookError w/ just the failing traceback.
723 try:
724 exec(compile(data, self._script_fullpath, 'exec'), context)
725 except Exception:
726 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
727 (traceback.format_exc(), self._hook_type))
728
729 # Running the script should have defined a main() function.
730 if 'main' not in context:
731 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
732
733 # Call the main function in the hook. If the hook should cause the
734 # build to fail, it will raise an Exception. We'll catch that convert
735 # to a HookError w/ just the failing traceback.
736 try:
737 context['main'](**kwargs)
738 except Exception:
739 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
740 'above.' % (traceback.format_exc(), self._hook_type))
741
Doug Anderson37282b42011-03-04 11:54:18 -0800742 def _ExecuteHook(self, **kwargs):
743 """Actually execute the given hook.
744
745 This will run the hook's 'main' function in our python interpreter.
746
747 Args:
748 kwargs: Keyword arguments to pass to the hook. These are often specific
749 to the hook type. For instance, pre-upload hooks will contain
750 a project_list.
751 """
752 # Keep sys.path and CWD stashed away so that we can always restore them
753 # upon function exit.
754 orig_path = os.getcwd()
755 orig_syspath = sys.path
756
757 try:
758 # Always run hooks with CWD as topdir.
759 os.chdir(self._topdir)
760
761 # Put the hook dir as the first item of sys.path so hooks can do
762 # relative imports. We want to replace the repo dir as [0] so
763 # hooks can't import repo files.
764 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
765
Mike Frysingerf7c51602019-06-18 17:23:39 -0400766 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500767 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800768
Doug Anderson37282b42011-03-04 11:54:18 -0800769 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
770 # We don't actually want hooks to define their main with this argument--
771 # it's there to remind them that their hook should always take **kwargs.
772 # For instance, a pre-upload hook should be defined like:
773 # def main(project_list, **kwargs):
774 #
775 # This allows us to later expand the API without breaking old hooks.
776 kwargs = kwargs.copy()
777 kwargs['hook_should_take_kwargs'] = True
778
Mike Frysingerf7c51602019-06-18 17:23:39 -0400779 # See what version of python the hook has been written against.
780 data = open(self._script_fullpath).read()
781 interp = self._ExtractInterpFromShebang(data)
782 reexec = False
783 if interp:
784 prog = os.path.basename(interp)
785 if prog.startswith('python2') and sys.version_info.major != 2:
786 reexec = True
787 elif prog.startswith('python3') and sys.version_info.major == 2:
788 reexec = True
789
790 # Attempt to execute the hooks through the requested version of Python.
791 if reexec:
792 try:
793 self._ExecuteHookViaReexec(interp, context, **kwargs)
794 except OSError as e:
795 if e.errno == errno.ENOENT:
796 # We couldn't find the interpreter, so fallback to importing.
797 reexec = False
798 else:
799 raise
800
801 # Run the hook by importing directly.
802 if not reexec:
803 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800804 finally:
805 # Restore sys.path and CWD.
806 sys.path = orig_syspath
807 os.chdir(orig_path)
808
809 def Run(self, user_allows_all_hooks, **kwargs):
810 """Run the hook.
811
812 If the hook doesn't exist (because there is no hooks project or because
813 this particular hook is not enabled), this is a no-op.
814
815 Args:
816 user_allows_all_hooks: If True, we will never prompt about running the
817 hook--we'll just assume it's OK to run it.
818 kwargs: Keyword arguments to pass to the hook. These are often specific
819 to the hook type. For instance, pre-upload hooks will contain
820 a project_list.
821
822 Raises:
823 HookError: If there was a problem finding the hook or the user declined
824 to run a required hook (from _CheckForHookApproval).
825 """
826 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700827 if ((not self._hooks_project) or (self._hook_type not in
828 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800829 return
830
831 # Bail with a nice error if we can't find the hook.
832 if not os.path.isfile(self._script_fullpath):
833 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
834
835 # Make sure the user is OK with running the hook.
836 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
837 return
838
839 # Run the hook with the same version of python we're using.
840 self._ExecuteHook(**kwargs)
841
842
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600844 # These objects can be shared between several working trees.
845 shareable_files = ['description', 'info']
846 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
847 # These objects can only be used by a single working tree.
848 working_tree_files = ['config', 'packed-refs', 'shallow']
849 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700850
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 def __init__(self,
852 manifest,
853 name,
854 remote,
855 gitdir,
David James8d201162013-10-11 17:03:19 -0700856 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 worktree,
858 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700859 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800860 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100861 rebase=True,
862 groups=None,
863 sync_c=False,
864 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900865 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100866 clone_depth=None,
867 upstream=None,
868 parent=None,
869 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900870 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700871 optimized_fetch=False,
872 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800873 """Init a Project object.
874
875 Args:
876 manifest: The XmlManifest object.
877 name: The `name` attribute of manifest.xml's project element.
878 remote: RemoteSpec object specifying its remote's properties.
879 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700880 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800881 worktree: Absolute path of git working tree.
882 relpath: Relative path of git working tree to repo's top directory.
883 revisionExpr: The `revision` attribute of manifest.xml's project element.
884 revisionId: git commit id for checking out.
885 rebase: The `rebase` attribute of manifest.xml's project element.
886 groups: The `groups` attribute of manifest.xml's project element.
887 sync_c: The `sync-c` attribute of manifest.xml's project element.
888 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900889 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800890 upstream: The `upstream` attribute of manifest.xml's project element.
891 parent: The parent Project object.
892 is_derived: False if the project was explicitly defined in the manifest;
893 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400894 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900895 optimized_fetch: If True, when a project is set to a sha1 revision, only
896 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700897 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800898 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 self.manifest = manifest
900 self.name = name
901 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800902 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700903 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800904 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700905 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800906 else:
907 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700909 self.revisionExpr = revisionExpr
910
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700911 if revisionId is None \
912 and revisionExpr \
913 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700914 self.revisionId = revisionExpr
915 else:
916 self.revisionId = revisionId
917
Mike Pontillod3153822012-02-28 11:53:24 -0800918 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700919 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700920 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800921 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900922 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900923 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700924 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800925 self.parent = parent
926 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900927 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800928 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800929
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500932 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500933 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700934 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
935 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800937 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700938 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800939 else:
940 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700941 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700942 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700943 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400944 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700945 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946
Doug Anderson37282b42011-03-04 11:54:18 -0800947 # This will be filled in if a project is later identified to be the
948 # project containing repo hooks.
949 self.enabled_repo_hooks = []
950
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800952 def Derived(self):
953 return self.is_derived
954
955 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700957 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958
959 @property
960 def CurrentBranch(self):
961 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400962
963 The branch name omits the 'refs/heads/' prefix.
964 None is returned if the project is on a detached HEAD, or if the work_git is
965 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400967 try:
968 b = self.work_git.GetHead()
969 except NoManifestException:
970 # If the local checkout is in a bad state, don't barf. Let the callers
971 # process this like the head is unreadable.
972 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 if b.startswith(R_HEADS):
974 return b[len(R_HEADS):]
975 return None
976
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700977 def IsRebaseInProgress(self):
978 w = self.worktree
979 g = os.path.join(w, '.git')
980 return os.path.exists(os.path.join(g, 'rebase-apply')) \
981 or os.path.exists(os.path.join(g, 'rebase-merge')) \
982 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200983
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 def IsDirty(self, consider_untracked=True):
985 """Is the working directory modified in some way?
986 """
987 self.work_git.update_index('-q',
988 '--unmerged',
989 '--ignore-missing',
990 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900991 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 return True
993 if self.work_git.DiffZ('diff-files'):
994 return True
995 if consider_untracked and self.work_git.LsOthers():
996 return True
997 return False
998
999 _userident_name = None
1000 _userident_email = None
1001
1002 @property
1003 def UserName(self):
1004 """Obtain the user's personal name.
1005 """
1006 if self._userident_name is None:
1007 self._LoadUserIdentity()
1008 return self._userident_name
1009
1010 @property
1011 def UserEmail(self):
1012 """Obtain the user's email address. This is very likely
1013 to be their Gerrit login.
1014 """
1015 if self._userident_email is None:
1016 self._LoadUserIdentity()
1017 return self._userident_email
1018
1019 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001020 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1021 m = re.compile("^(.*) <([^>]*)> ").match(u)
1022 if m:
1023 self._userident_name = m.group(1)
1024 self._userident_email = m.group(2)
1025 else:
1026 self._userident_name = ''
1027 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028
1029 def GetRemote(self, name):
1030 """Get the configuration for a single remote.
1031 """
1032 return self.config.GetRemote(name)
1033
1034 def GetBranch(self, name):
1035 """Get the configuration for a single branch.
1036 """
1037 return self.config.GetBranch(name)
1038
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001039 def GetBranches(self):
1040 """Get all existing local branches.
1041 """
1042 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001044 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001045
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301046 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001047 if name.startswith(R_HEADS):
1048 name = name[len(R_HEADS):]
1049 b = self.GetBranch(name)
1050 b.current = name == current
1051 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001052 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001053 heads[name] = b
1054
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301055 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001056 if name.startswith(R_PUB):
1057 name = name[len(R_PUB):]
1058 b = heads.get(name)
1059 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001060 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001061
1062 return heads
1063
Colin Cross5acde752012-03-28 20:15:45 -07001064 def MatchesGroups(self, manifest_groups):
1065 """Returns true if the manifest groups specified at init should cause
1066 this project to be synced.
1067 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001068 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001069
1070 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001071 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001072 manifest_groups: "-group1,group2"
1073 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001074
1075 The special manifest group "default" will match any project that
1076 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001077 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001078 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001079 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001080 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001081 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001082
Conley Owens971de8e2012-04-16 10:36:08 -07001083 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001084 for group in expanded_manifest_groups:
1085 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001086 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001087 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001088 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001089
Conley Owens971de8e2012-04-16 10:36:08 -07001090 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001092# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001093 def UncommitedFiles(self, get_all=True):
1094 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001096 Args:
1097 get_all: a boolean, if True - get information about all different
1098 uncommitted files. If False - return as soon as any kind of
1099 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001100 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001101 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001102 self.work_git.update_index('-q',
1103 '--unmerged',
1104 '--ignore-missing',
1105 '--refresh')
1106 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 details.append("rebase in progress")
1108 if not get_all:
1109 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001110
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001111 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1112 if changes:
1113 details.extend(changes)
1114 if not get_all:
1115 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001116
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001117 changes = self.work_git.DiffZ('diff-files').keys()
1118 if changes:
1119 details.extend(changes)
1120 if not get_all:
1121 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001122
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001123 changes = self.work_git.LsOthers()
1124 if changes:
1125 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001126
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001127 return details
1128
1129 def HasChanges(self):
1130 """Returns true if there are uncommitted changes.
1131 """
1132 if self.UncommitedFiles(get_all=False):
1133 return True
1134 else:
1135 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001136
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001137 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001139
1140 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001141 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001142 quiet: If True then only print the project name. Do not print
1143 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001145 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001146 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001147 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001148 print(file=output_redir)
1149 print('project %s/' % self.relpath, file=output_redir)
1150 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 return
1152
1153 self.work_git.update_index('-q',
1154 '--unmerged',
1155 '--ignore-missing',
1156 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001157 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1159 df = self.work_git.DiffZ('diff-files')
1160 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001161 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001162 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163
1164 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001165 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001166 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001167 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001169 if quiet:
1170 out.nl()
1171 return 'DIRTY'
1172
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173 branch = self.CurrentBranch
1174 if branch is None:
1175 out.nobranch('(*** NO BRANCH ***)')
1176 else:
1177 out.branch('branch %s', branch)
1178 out.nl()
1179
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001180 if rb:
1181 out.important('prior sync failed; rebase still in progress')
1182 out.nl()
1183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184 paths = list()
1185 paths.extend(di.keys())
1186 paths.extend(df.keys())
1187 paths.extend(do)
1188
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301189 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001190 try:
1191 i = di[p]
1192 except KeyError:
1193 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001195 try:
1196 f = df[p]
1197 except KeyError:
1198 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001199
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001200 if i:
1201 i_status = i.status.upper()
1202 else:
1203 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001205 if f:
1206 f_status = f.status.lower()
1207 else:
1208 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209
1210 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001211 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001212 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 else:
1214 line = ' %s%s\t%s' % (i_status, f_status, p)
1215
1216 if i and not f:
1217 out.added('%s', line)
1218 elif (i and f) or (not i and f):
1219 out.changed('%s', line)
1220 elif not i and not f:
1221 out.untracked('%s', line)
1222 else:
1223 out.write('%s', line)
1224 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001225
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001226 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
pelyad67872d2012-03-28 14:49:58 +03001228 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 """Prints the status of the repository to stdout.
1230 """
1231 out = DiffColoring(self.config)
1232 cmd = ['diff']
1233 if out.is_on:
1234 cmd.append('--color')
1235 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001236 if absolute_paths:
1237 cmd.append('--src-prefix=a/%s/' % self.relpath)
1238 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001240 try:
1241 p = GitCommand(self,
1242 cmd,
1243 capture_stdout=True,
1244 capture_stderr=True)
1245 except GitError as e:
1246 out.nl()
1247 out.project('project %s/' % self.relpath)
1248 out.nl()
1249 out.fail('%s', str(e))
1250 out.nl()
1251 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252 has_diff = False
1253 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001254 if not hasattr(line, 'encode'):
1255 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 if not has_diff:
1257 out.nl()
1258 out.project('project %s/' % self.relpath)
1259 out.nl()
1260 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001261 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001262 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001264# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001265 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266 """Was the branch published (uploaded) for code review?
1267 If so, returns the SHA-1 hash of the last published
1268 state for the branch.
1269 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001270 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001272 try:
1273 return self.bare_git.rev_parse(key)
1274 except GitError:
1275 return None
1276 else:
1277 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001278 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001279 except KeyError:
1280 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281
David Pursehouse8a68ff92012-09-24 12:15:13 +09001282 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283 """Prunes any stale published refs.
1284 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001285 if all_refs is None:
1286 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 heads = set()
1288 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301289 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290 if name.startswith(R_HEADS):
1291 heads.add(name)
1292 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001293 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301295 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 n = name[len(R_PUB):]
1297 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001298 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001300 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 """List any branches which can be uploaded for review.
1302 """
1303 heads = {}
1304 pubed = {}
1305
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301306 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001308 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311
1312 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301313 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001314 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001316 if selected_branch and branch != selected_branch:
1317 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001319 rb = self.GetUploadableBranch(branch)
1320 if rb:
1321 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 return ready
1323
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001324 def GetUploadableBranch(self, branch_name):
1325 """Get a single uploadable branch, or None.
1326 """
1327 branch = self.GetBranch(branch_name)
1328 base = branch.LocalMerge
1329 if branch.LocalMerge:
1330 rb = ReviewableBranch(self, branch, base)
1331 if rb.commits:
1332 return rb
1333 return None
1334
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001335 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001336 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001337 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001338 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001339 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001340 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001341 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001342 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001343 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001344 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001345 validate_certs=True,
1346 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347 """Uploads the named branch for code review.
1348 """
1349 if branch is None:
1350 branch = self.CurrentBranch
1351 if branch is None:
1352 raise GitError('not currently on a branch')
1353
1354 branch = self.GetBranch(branch)
1355 if not branch.LocalMerge:
1356 raise GitError('branch %s does not track a remote' % branch.name)
1357 if not branch.remote.review:
1358 raise GitError('remote %s has no review url' % branch.remote.name)
1359
Bryan Jacobsf609f912013-05-06 13:36:24 -04001360 if dest_branch is None:
1361 dest_branch = self.dest_branch
1362 if dest_branch is None:
1363 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 if not dest_branch.startswith(R_HEADS):
1365 dest_branch = R_HEADS + dest_branch
1366
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001367 if not branch.remote.projectname:
1368 branch.remote.projectname = self.name
1369 branch.remote.Save()
1370
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001371 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001372 if url is None:
1373 raise UploadError('review not configured')
1374 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001375 if dryrun:
1376 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001377
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001378 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001379 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001380
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001381 for push_option in (push_options or []):
1382 cmd.append('-o')
1383 cmd.append(push_option)
1384
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001385 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001386
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001387 if dest_branch.startswith(R_HEADS):
1388 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001389
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001390 upload_type = 'for'
1391 if draft:
1392 upload_type = 'drafts'
1393
1394 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1395 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001396 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001397 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001398 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001399 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001400
David Pursehousef25a3702018-11-14 19:01:22 -08001401 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001402 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001403 if notify:
1404 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001405 if private:
1406 opts += ['private']
1407 if wip:
1408 opts += ['wip']
1409 if opts:
1410 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001411 cmd.append(ref_spec)
1412
Anthony King7bdac712014-07-16 12:56:40 +01001413 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001414 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415
1416 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1417 self.bare_git.UpdateRef(R_PUB + branch.name,
1418 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001419 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001421# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001422 def _ExtractArchive(self, tarpath, path=None):
1423 """Extract the given tar on its current location
1424
1425 Args:
1426 - tarpath: The path to the actual tar file
1427
1428 """
1429 try:
1430 with tarfile.open(tarpath, 'r') as tar:
1431 tar.extractall(path=path)
1432 return True
1433 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001434 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001435 return False
1436
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001437 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001438 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001439 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001440 is_new=None,
1441 current_branch_only=False,
1442 force_sync=False,
1443 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001444 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001445 archive=False,
1446 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001447 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001448 submodules=False,
1449 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001450 """Perform only the network IO portion of the sync process.
1451 Local working directory/branch state is not affected.
1452 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001453 if archive and not isinstance(self, MetaProject):
1454 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001455 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001456 return False
1457
1458 name = self.relpath.replace('\\', '/')
1459 name = name.replace('/', '_')
1460 tarpath = '%s.tar' % name
1461 topdir = self.manifest.topdir
1462
1463 try:
1464 self._FetchArchive(tarpath, cwd=topdir)
1465 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001466 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001467 return False
1468
1469 # From now on, we only need absolute tarpath
1470 tarpath = os.path.join(topdir, tarpath)
1471
1472 if not self._ExtractArchive(tarpath, path=topdir):
1473 return False
1474 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001475 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001476 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001477 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001478 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001479 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001480 if is_new is None:
1481 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001482 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001483 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001484 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001485 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001487
1488 if is_new:
1489 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1490 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001491 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001492 # This works for both absolute and relative alternate directories.
1493 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001494 except IOError:
1495 alt_dir = None
1496 else:
1497 alt_dir = None
1498
Mike Frysingere50b6a72020-02-19 01:45:48 -05001499 if (clone_bundle
1500 and alt_dir is None
1501 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001502 is_new = False
1503
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001504 if not current_branch_only:
1505 if self.sync_c:
1506 current_branch_only = True
1507 elif not self.manifest._loaded:
1508 # Manifest cannot check defaults until it syncs.
1509 current_branch_only = False
1510 elif self.manifest.default.sync_c:
1511 current_branch_only = True
1512
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001513 if not self.sync_tags:
1514 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001515
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001516 if self.clone_depth:
1517 depth = self.clone_depth
1518 else:
1519 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1520
Mike Frysinger521d01b2020-02-17 01:51:49 -05001521 # See if we can skip the network fetch entirely.
1522 if not (optimized_fetch and
1523 (ID_RE.match(self.revisionExpr) and
1524 self._CheckForImmutableRevision())):
1525 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001526 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1527 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001528 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001529 submodules=submodules, force_sync=force_sync,
1530 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001531 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001532
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001533 mp = self.manifest.manifestProject
1534 dissociate = mp.config.GetBoolean('repo.dissociate')
1535 if dissociate:
1536 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1537 if os.path.exists(alternates_file):
1538 cmd = ['repack', '-a', '-d']
1539 if GitCommand(self, cmd, bare=True).Wait() != 0:
1540 return False
1541 platform_utils.remove(alternates_file)
1542
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001543 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001544 self._InitMRef()
1545 else:
1546 self._InitMirrorHead()
1547 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001548 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001549 except OSError:
1550 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001552
1553 def PostRepoUpgrade(self):
1554 self._InitHooks()
1555
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001556 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001557 if self.manifest.isGitcClient:
1558 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001559 for copyfile in self.copyfiles:
1560 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001561 for linkfile in self.linkfiles:
1562 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563
Julien Camperguedd654222014-01-09 16:21:37 +01001564 def GetCommitRevisionId(self):
1565 """Get revisionId of a commit.
1566
1567 Use this method instead of GetRevisionId to get the id of the commit rather
1568 than the id of the current git object (for example, a tag)
1569
1570 """
1571 if not self.revisionExpr.startswith(R_TAGS):
1572 return self.GetRevisionId(self._allrefs)
1573
1574 try:
1575 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1576 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001577 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1578 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001579
David Pursehouse8a68ff92012-09-24 12:15:13 +09001580 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001581 if self.revisionId:
1582 return self.revisionId
1583
1584 rem = self.GetRemote(self.remote.name)
1585 rev = rem.ToLocal(self.revisionExpr)
1586
David Pursehouse8a68ff92012-09-24 12:15:13 +09001587 if all_refs is not None and rev in all_refs:
1588 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001589
1590 try:
1591 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1592 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001593 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1594 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001595
Martin Kellye4e94d22017-03-21 16:05:12 -07001596 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597 """Perform only the local IO portion of the sync process.
1598 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001600 if not os.path.exists(self.gitdir):
1601 syncbuf.fail(self,
1602 'Cannot checkout %s due to missing network sync; Run '
1603 '`repo sync -n %s` first.' %
1604 (self.name, self.name))
1605 return
1606
Martin Kellye4e94d22017-03-21 16:05:12 -07001607 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001608 all_refs = self.bare_ref.all
1609 self.CleanPublishedCache(all_refs)
1610 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001611
David Pursehouse1d947b32012-10-25 12:23:11 +09001612 def _doff():
1613 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001614 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001615
Martin Kellye4e94d22017-03-21 16:05:12 -07001616 def _dosubmodules():
1617 self._SyncSubmodules(quiet=True)
1618
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001619 head = self.work_git.GetHead()
1620 if head.startswith(R_HEADS):
1621 branch = head[len(R_HEADS):]
1622 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001624 except KeyError:
1625 head = None
1626 else:
1627 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001628
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001629 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001630 # Currently on a detached HEAD. The user is assumed to
1631 # not have any local modifications worth worrying about.
1632 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001633 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001634 syncbuf.fail(self, _PriorSyncFailedError())
1635 return
1636
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001637 if head == revid:
1638 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001639 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001640 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001641 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001642 # The copy/linkfile config may have changed.
1643 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001644 return
1645 else:
1646 lost = self._revlist(not_rev(revid), HEAD)
1647 if lost:
1648 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001649
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001651 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001652 if submodules:
1653 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001654 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001655 syncbuf.fail(self, e)
1656 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001657 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001658 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001660 if head == revid:
1661 # No changes; don't do anything further.
1662 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001663 # The copy/linkfile config may have changed.
1664 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001665 return
1666
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001668
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001669 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001671 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001672 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001673 syncbuf.info(self,
1674 "leaving %s; does not track upstream",
1675 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001677 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001678 if submodules:
1679 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001680 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001681 syncbuf.fail(self, e)
1682 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001683 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001684 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001686 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001687
1688 # See if we can perform a fast forward merge. This can happen if our
1689 # branch isn't in the exact same state as we last published.
1690 try:
1691 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1692 # Skip the published logic.
1693 pub = False
1694 except GitError:
1695 pub = self.WasPublished(branch.name, all_refs)
1696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001698 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699 if not_merged:
1700 if upstream_gain:
1701 # The user has published this branch and some of those
1702 # commits are not yet merged upstream. We do not want
1703 # to rewrite the published commits so we punt.
1704 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001705 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001706 "branch %s is published (but not merged) and is now "
1707 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001708 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001709 elif pub == head:
1710 # All published commits are merged, and thus we are a
1711 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001712 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001713 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001714 if submodules:
1715 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001716 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001718 # Examine the local commits not in the remote. Find the
1719 # last one attributed to this user, if any.
1720 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001721 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001722 last_mine = None
1723 cnt_mine = 0
1724 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001725 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001726 if committer_email == self.UserEmail:
1727 last_mine = commit_id
1728 cnt_mine += 1
1729
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001730 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001731 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732
1733 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001734 syncbuf.fail(self, _DirtyError())
1735 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001737 # If the upstream switched on us, warn the user.
1738 #
1739 if branch.merge != self.revisionExpr:
1740 if branch.merge and self.revisionExpr:
1741 syncbuf.info(self,
1742 'manifest switched %s...%s',
1743 branch.merge,
1744 self.revisionExpr)
1745 elif branch.merge:
1746 syncbuf.info(self,
1747 'manifest no longer tracks %s',
1748 branch.merge)
1749
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001750 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001752 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001754 syncbuf.info(self,
1755 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001756 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001758 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001759 if not ID_RE.match(self.revisionExpr):
1760 # in case of manifest sync the revisionExpr might be a SHA1
1761 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001762 if not branch.merge.startswith('refs/'):
1763 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 branch.Save()
1765
Mike Pontillod3153822012-02-28 11:53:24 -08001766 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001767 def _docopyandlink():
1768 self._CopyAndLinkFiles()
1769
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001770 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001771 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001772 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001773 if submodules:
1774 syncbuf.later2(self, _dosubmodules)
1775 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001776 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001778 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001779 if submodules:
1780 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001781 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001782 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001783 syncbuf.fail(self, e)
1784 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001786 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001787 if submodules:
1788 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789
Mike Frysingere6a202f2019-08-02 15:57:57 -04001790 def AddCopyFile(self, src, dest, topdir):
1791 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792
Mike Frysingere6a202f2019-08-02 15:57:57 -04001793 No filesystem changes occur here. Actual copying happens later on.
1794
1795 Paths should have basic validation run on them before being queued.
1796 Further checking will be handled when the actual copy happens.
1797 """
1798 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1799
1800 def AddLinkFile(self, src, dest, topdir):
1801 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1802
1803 No filesystem changes occur here. Actual linking happens later on.
1804
1805 Paths should have basic validation run on them before being queued.
1806 Further checking will be handled when the actual link happens.
1807 """
1808 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001809
James W. Mills24c13082012-04-12 15:04:13 -05001810 def AddAnnotation(self, name, value, keep):
1811 self.annotations.append(_Annotation(name, value, keep))
1812
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001813 def DownloadPatchSet(self, change_id, patch_id):
1814 """Download a single patch set of a single change to FETCH_HEAD.
1815 """
1816 remote = self.GetRemote(self.remote.name)
1817
1818 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001819 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001820 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001821 if GitCommand(self, cmd, bare=True).Wait() != 0:
1822 return None
1823 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001824 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001825 change_id,
1826 patch_id,
1827 self.bare_git.rev_parse('FETCH_HEAD'))
1828
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001829# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001830 def GetHeadPath(self):
1831 """Return the full path to the HEAD ref."""
1832 dotgit = os.path.join(self.worktree, '.git')
1833 if os.path.isfile(dotgit):
1834 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1835 with open(dotgit) as fp:
1836 setting = fp.read()
1837 assert setting.startswith('gitdir:')
1838 gitdir = setting.split(':', 1)[1].strip()
1839 dotgit = os.path.join(self.worktree, gitdir)
1840 return os.path.join(dotgit, HEAD)
1841
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001842 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001843 """Create a new branch off the manifest's revision.
1844 """
Simran Basib9a1b732015-08-20 12:19:28 -07001845 if not branch_merge:
1846 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001847 head = self.work_git.GetHead()
1848 if head == (R_HEADS + name):
1849 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001850
David Pursehouse8a68ff92012-09-24 12:15:13 +09001851 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001852 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001853 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001854 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001855 capture_stdout=True,
1856 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001857
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001858 branch = self.GetBranch(name)
1859 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001860 branch.merge = branch_merge
1861 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1862 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001863
1864 if revision is None:
1865 revid = self.GetRevisionId(all_refs)
1866 else:
1867 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001868
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001869 if head.startswith(R_HEADS):
1870 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001871 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001872 except KeyError:
1873 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001874 if revid and head and revid == head:
1875 ref = os.path.join(self.gitdir, R_HEADS + name)
1876 try:
1877 os.makedirs(os.path.dirname(ref))
1878 except OSError:
1879 pass
1880 _lwrite(ref, '%s\n' % revid)
Mike Frysingerf914edc2020-02-09 03:01:56 -05001881 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001882 branch.Save()
1883 return True
1884
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001885 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001886 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001887 capture_stdout=True,
1888 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001889 branch.Save()
1890 return True
1891 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001892
Wink Saville02d79452009-04-10 13:01:24 -07001893 def CheckoutBranch(self, name):
1894 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001895
1896 Args:
1897 name: The name of the branch to checkout.
1898
1899 Returns:
1900 True if the checkout succeeded; False if it didn't; None if the branch
1901 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001902 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001903 rev = R_HEADS + name
1904 head = self.work_git.GetHead()
1905 if head == rev:
1906 # Already on the branch
1907 #
1908 return True
Wink Saville02d79452009-04-10 13:01:24 -07001909
David Pursehouse8a68ff92012-09-24 12:15:13 +09001910 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001911 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001912 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001913 except KeyError:
1914 # Branch does not exist in this project
1915 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001916 return None
Wink Saville02d79452009-04-10 13:01:24 -07001917
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001918 if head.startswith(R_HEADS):
1919 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001920 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001921 except KeyError:
1922 head = None
1923
1924 if head == revid:
1925 # Same revision; just update HEAD to point to the new
1926 # target branch, but otherwise take no other action.
1927 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001928 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001929 return True
1930
1931 return GitCommand(self,
1932 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001933 capture_stdout=True,
1934 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001935
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001936 def AbandonBranch(self, name):
1937 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001938
1939 Args:
1940 name: The name of the branch to abandon.
1941
1942 Returns:
1943 True if the abandon succeeded; False if it didn't; None if the branch
1944 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001945 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001946 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001947 all_refs = self.bare_ref.all
1948 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001949 # Doesn't exist
1950 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001951
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001952 head = self.work_git.GetHead()
1953 if head == rev:
1954 # We can't destroy the branch while we are sitting
1955 # on it. Switch to a detached HEAD.
1956 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001957 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001958
David Pursehouse8a68ff92012-09-24 12:15:13 +09001959 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001960 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05001961 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001962 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001963 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001964
1965 return GitCommand(self,
1966 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001967 capture_stdout=True,
1968 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001969
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970 def PruneHeads(self):
1971 """Prune any topic branches already merged into upstream.
1972 """
1973 cb = self.CurrentBranch
1974 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001975 left = self._allrefs
1976 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001977 if name.startswith(R_HEADS):
1978 name = name[len(R_HEADS):]
1979 if cb is None or name != cb:
1980 kill.append(name)
1981
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001982 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001983 if cb is not None \
1984 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001985 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001986 self.work_git.DetachHead(HEAD)
1987 kill.append(cb)
1988
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001989 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001990 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001991
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001992 try:
1993 self.bare_git.DetachHead(rev)
1994
1995 b = ['branch', '-d']
1996 b.extend(kill)
1997 b = GitCommand(self, b, bare=True,
1998 capture_stdout=True,
1999 capture_stderr=True)
2000 b.Wait()
2001 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002002 if ID_RE.match(old):
2003 self.bare_git.DetachHead(old)
2004 else:
2005 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002006 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002008 for branch in kill:
2009 if (R_HEADS + branch) not in left:
2010 self.CleanPublishedCache()
2011 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002012
2013 if cb and cb not in kill:
2014 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002015 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002016
2017 kept = []
2018 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002019 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002020 branch = self.GetBranch(branch)
2021 base = branch.LocalMerge
2022 if not base:
2023 base = rev
2024 kept.append(ReviewableBranch(self, branch, base))
2025 return kept
2026
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002027# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002028 def GetRegisteredSubprojects(self):
2029 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002030
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002031 def rec(subprojects):
2032 if not subprojects:
2033 return
2034 result.extend(subprojects)
2035 for p in subprojects:
2036 rec(p.subprojects)
2037 rec(self.subprojects)
2038 return result
2039
2040 def _GetSubmodules(self):
2041 # Unfortunately we cannot call `git submodule status --recursive` here
2042 # because the working tree might not exist yet, and it cannot be used
2043 # without a working tree in its current implementation.
2044
2045 def get_submodules(gitdir, rev):
2046 # Parse .gitmodules for submodule sub_paths and sub_urls
2047 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2048 if not sub_paths:
2049 return []
2050 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2051 # revision of submodule repository
2052 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2053 submodules = []
2054 for sub_path, sub_url in zip(sub_paths, sub_urls):
2055 try:
2056 sub_rev = sub_revs[sub_path]
2057 except KeyError:
2058 # Ignore non-exist submodules
2059 continue
2060 submodules.append((sub_rev, sub_path, sub_url))
2061 return submodules
2062
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002063 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2064 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002065
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002066 def parse_gitmodules(gitdir, rev):
2067 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2068 try:
Anthony King7bdac712014-07-16 12:56:40 +01002069 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2070 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002071 except GitError:
2072 return [], []
2073 if p.Wait() != 0:
2074 return [], []
2075
2076 gitmodules_lines = []
2077 fd, temp_gitmodules_path = tempfile.mkstemp()
2078 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002079 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002080 os.close(fd)
2081 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002082 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2083 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002084 if p.Wait() != 0:
2085 return [], []
2086 gitmodules_lines = p.stdout.split('\n')
2087 except GitError:
2088 return [], []
2089 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002090 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002091
2092 names = set()
2093 paths = {}
2094 urls = {}
2095 for line in gitmodules_lines:
2096 if not line:
2097 continue
2098 m = re_path.match(line)
2099 if m:
2100 names.add(m.group(1))
2101 paths[m.group(1)] = m.group(2)
2102 continue
2103 m = re_url.match(line)
2104 if m:
2105 names.add(m.group(1))
2106 urls[m.group(1)] = m.group(2)
2107 continue
2108 names = sorted(names)
2109 return ([paths.get(name, '') for name in names],
2110 [urls.get(name, '') for name in names])
2111
2112 def git_ls_tree(gitdir, rev, paths):
2113 cmd = ['ls-tree', rev, '--']
2114 cmd.extend(paths)
2115 try:
Anthony King7bdac712014-07-16 12:56:40 +01002116 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2117 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002118 except GitError:
2119 return []
2120 if p.Wait() != 0:
2121 return []
2122 objects = {}
2123 for line in p.stdout.split('\n'):
2124 if not line.strip():
2125 continue
2126 object_rev, object_path = line.split()[2:4]
2127 objects[object_path] = object_rev
2128 return objects
2129
2130 try:
2131 rev = self.GetRevisionId()
2132 except GitError:
2133 return []
2134 return get_submodules(self.gitdir, rev)
2135
2136 def GetDerivedSubprojects(self):
2137 result = []
2138 if not self.Exists:
2139 # If git repo does not exist yet, querying its submodules will
2140 # mess up its states; so return here.
2141 return result
2142 for rev, path, url in self._GetSubmodules():
2143 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002144 relpath, worktree, gitdir, objdir = \
2145 self.manifest.GetSubprojectPaths(self, name, path)
2146 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002147 if project:
2148 result.extend(project.GetDerivedSubprojects())
2149 continue
David James8d201162013-10-11 17:03:19 -07002150
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002151 if url.startswith('..'):
2152 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002153 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002154 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002155 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002156 review=self.remote.review,
2157 revision=self.remote.revision)
2158 subproject = Project(manifest=self.manifest,
2159 name=name,
2160 remote=remote,
2161 gitdir=gitdir,
2162 objdir=objdir,
2163 worktree=worktree,
2164 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002165 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002166 revisionId=rev,
2167 rebase=self.rebase,
2168 groups=self.groups,
2169 sync_c=self.sync_c,
2170 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002171 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002172 parent=self,
2173 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002174 result.append(subproject)
2175 result.extend(subproject.GetDerivedSubprojects())
2176 return result
2177
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002178# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002179 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002180 try:
2181 # if revision (sha or tag) is not present then following function
2182 # throws an error.
2183 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2184 return True
2185 except GitError:
2186 # There is no such persistent revision. We have to fetch it.
2187 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002188
Julien Campergue335f5ef2013-10-16 11:02:35 +02002189 def _FetchArchive(self, tarpath, cwd=None):
2190 cmd = ['archive', '-v', '-o', tarpath]
2191 cmd.append('--remote=%s' % self.remote.url)
2192 cmd.append('--prefix=%s/' % self.relpath)
2193 cmd.append(self.revisionExpr)
2194
2195 command = GitCommand(self, cmd, cwd=cwd,
2196 capture_stdout=True,
2197 capture_stderr=True)
2198
2199 if command.Wait() != 0:
2200 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2201
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002202 def _RemoteFetch(self, name=None,
2203 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002204 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002205 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002206 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002207 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002208 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002209 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002210 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002211 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002212 force_sync=False,
2213 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002214
2215 is_sha1 = False
2216 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002217 # The depth should not be used when fetching to a mirror because
2218 # it will result in a shallow repository that cannot be cloned or
2219 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002220 # The repo project should also never be synced with partial depth.
2221 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2222 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002223
Shawn Pearce69e04d82014-01-29 12:48:54 -08002224 if depth:
2225 current_branch_only = True
2226
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002227 if ID_RE.match(self.revisionExpr) is not None:
2228 is_sha1 = True
2229
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002230 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002231 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002232 # this is a tag and its sha1 value should never change
2233 tag_name = self.revisionExpr[len(R_TAGS):]
2234
2235 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002236 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002237 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002238 print('Skipped fetching project %s (already have persistent ref)'
2239 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002240 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002241 if is_sha1 and not depth:
2242 # When syncing a specific commit and --depth is not set:
2243 # * if upstream is explicitly specified and is not a sha1, fetch only
2244 # upstream as users expect only upstream to be fetch.
2245 # Note: The commit might not be in upstream in which case the sync
2246 # will fail.
2247 # * otherwise, fetch all branches to make sure we end up with the
2248 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002249 if self.upstream:
2250 current_branch_only = not ID_RE.match(self.upstream)
2251 else:
2252 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002254 if not name:
2255 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002256
2257 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002258 remote = self.GetRemote(name)
2259 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002260 ssh_proxy = True
2261
Shawn O. Pearce88443382010-10-08 10:02:09 +02002262 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002263 if alt_dir and 'objects' == os.path.basename(alt_dir):
2264 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002265 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2266 remote = self.GetRemote(name)
2267
David Pursehouse8a68ff92012-09-24 12:15:13 +09002268 all_refs = self.bare_ref.all
2269 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002270 tmp = set()
2271
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302272 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002273 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002274 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002275 all_refs[r] = ref_id
2276 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002277 continue
2278
David Pursehouse8a68ff92012-09-24 12:15:13 +09002279 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002280 continue
2281
David Pursehouse8a68ff92012-09-24 12:15:13 +09002282 r = 'refs/_alt/%s' % ref_id
2283 all_refs[r] = ref_id
2284 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002285 tmp.add(r)
2286
heping3d7bbc92017-04-12 19:51:47 +08002287 tmp_packed_lines = []
2288 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002289
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302290 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002291 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002292 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002294 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002295
heping3d7bbc92017-04-12 19:51:47 +08002296 tmp_packed = ''.join(tmp_packed_lines)
2297 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002298 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002299 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002300 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002301
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002302 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002303
Xin Li745be2e2019-06-03 11:24:30 -07002304 if clone_filter:
2305 git_require((2, 19, 0), fail=True, msg='partial clones')
2306 cmd.append('--filter=%s' % clone_filter)
2307 self.config.SetString('extensions.partialclone', self.remote.name)
2308
Conley Owensf97e8382015-01-21 11:12:46 -08002309 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002310 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002311 else:
2312 # If this repo has shallow objects, then we don't know which refs have
2313 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2314 # do this with projects that don't have shallow objects, since it is less
2315 # efficient.
2316 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2317 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002318
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002319 if quiet:
2320 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002321 if not self.worktree:
2322 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002323 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002324
Mike Frysingere57f1142019-03-18 21:27:54 -04002325 if force_sync:
2326 cmd.append('--force')
2327
David Pursehouse74cfd272015-10-14 10:50:15 +09002328 if prune:
2329 cmd.append('--prune')
2330
Martin Kellye4e94d22017-03-21 16:05:12 -07002331 if submodules:
2332 cmd.append('--recurse-submodules=on-demand')
2333
Kuang-che Wu6856f982019-11-25 12:37:55 +08002334 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002335 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002336 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002337 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002338 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002339 spec.append('tag')
2340 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002341
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302342 if self.manifest.IsMirror and not current_branch_only:
2343 branch = None
2344 else:
2345 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002346 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002347 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002348 # Shallow checkout of a specific commit, fetch from that commit and not
2349 # the heads only as the commit might be deeper in the history.
2350 spec.append(branch)
2351 else:
2352 if is_sha1:
2353 branch = self.upstream
2354 if branch is not None and branch.strip():
2355 if not branch.startswith('refs/'):
2356 branch = R_HEADS + branch
2357 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2358
2359 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2360 # whole repo.
2361 if self.manifest.IsMirror and not spec:
2362 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2363
2364 # If using depth then we should not get all the tags since they may
2365 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002366 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002367 cmd.append('--no-tags')
2368 else:
2369 cmd.append('--tags')
2370 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2371
Conley Owens80b87fe2014-05-09 17:13:44 -07002372 cmd.extend(spec)
2373
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002374 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002375 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002376 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2377 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002378 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002379 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002380 ok = True
2381 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002382 # If needed, run the 'git remote prune' the first time through the loop
2383 elif (not _i and
2384 "error:" in gitcmd.stderr and
2385 "git remote prune" in gitcmd.stderr):
2386 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002387 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002388 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002389 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002390 break
2391 continue
Brian Harring14a66742012-09-28 20:21:57 -07002392 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002393 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2394 # in sha1 mode, we just tried sync'ing from the upstream field; it
2395 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002396 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002397 elif ret < 0:
2398 # Git died with a signal, exit immediately
2399 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002400 if not verbose:
2401 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002402 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002403
2404 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002405 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406 if old_packed != '':
2407 _lwrite(packed_refs, old_packed)
2408 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002409 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002410 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002411
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002412 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002413 # We just synced the upstream given branch; verify we
2414 # got what we wanted, else trigger a second run of all
2415 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002416 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002417 # Sync the current branch only with depth set to None.
2418 # We always pass depth=None down to avoid infinite recursion.
2419 return self._RemoteFetch(
2420 name=name, quiet=quiet, verbose=verbose,
2421 current_branch_only=current_branch_only and depth,
2422 initial=False, alt_dir=alt_dir,
2423 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002424
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002425 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002426
Mike Frysingere50b6a72020-02-19 01:45:48 -05002427 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002428 if initial and \
2429 (self.manifest.manifestProject.config.GetString('repo.depth') or
2430 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002431 return False
2432
2433 remote = self.GetRemote(self.remote.name)
2434 bundle_url = remote.url + '/clone.bundle'
2435 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002436 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2437 'persistent-http',
2438 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002439 return False
2440
2441 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2442 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2443
2444 exist_dst = os.path.exists(bundle_dst)
2445 exist_tmp = os.path.exists(bundle_tmp)
2446
2447 if not initial and not exist_dst and not exist_tmp:
2448 return False
2449
2450 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002451 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2452 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002453 if not exist_dst:
2454 return False
2455
2456 cmd = ['fetch']
2457 if quiet:
2458 cmd.append('--quiet')
2459 if not self.worktree:
2460 cmd.append('--update-head-ok')
2461 cmd.append(bundle_dst)
2462 for f in remote.fetch:
2463 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002464 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002465
2466 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002467 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002468 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002469 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002470 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002471 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472
Mike Frysingere50b6a72020-02-19 01:45:48 -05002473 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002474 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002475 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002476
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002477 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002478 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002479 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002480 if os.path.exists(tmpPath):
2481 size = os.stat(tmpPath).st_size
2482 if size >= 1024:
2483 cmd += ['--continue-at', '%d' % (size,)]
2484 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002485 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002486 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002487 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002488 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002489 if proxy:
2490 cmd += ['--proxy', proxy]
2491 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2492 cmd += ['--proxy', os.environ['http_proxy']]
2493 if srcUrl.startswith('persistent-https'):
2494 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2495 elif srcUrl.startswith('persistent-http'):
2496 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002497 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002498
Dave Borowitz137d0132015-01-02 11:12:54 -08002499 if IsTrace():
2500 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002501 if verbose:
2502 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2503 stdout = None if verbose else subprocess.PIPE
2504 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002505 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002506 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002507 except OSError:
2508 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002509
Mike Frysingere50b6a72020-02-19 01:45:48 -05002510 (output, _) = proc.communicate()
2511 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002512
Dave Borowitz137d0132015-01-02 11:12:54 -08002513 if curlret == 22:
2514 # From curl man page:
2515 # 22: HTTP page not retrieved. The requested url was not found or
2516 # returned another error with the HTTP error code being 400 or above.
2517 # This return code only appears if -f, --fail is used.
2518 if not quiet:
2519 print("Server does not provide clone.bundle; ignoring.",
2520 file=sys.stderr)
2521 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002522 elif curlret and not verbose and output:
2523 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002524
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002525 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002526 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002527 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002528 return True
2529 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002530 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002531 return False
2532 else:
2533 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002534
Kris Giesingc8d882a2014-12-23 13:02:32 -08002535 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002536 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002537 with open(path, 'rb') as f:
2538 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002539 return True
2540 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002541 if not quiet:
2542 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002543 return False
2544 except OSError:
2545 return False
2546
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002547 def _Checkout(self, rev, quiet=False):
2548 cmd = ['checkout']
2549 if quiet:
2550 cmd.append('-q')
2551 cmd.append(rev)
2552 cmd.append('--')
2553 if GitCommand(self, cmd).Wait() != 0:
2554 if self._allrefs:
2555 raise GitError('%s checkout %s ' % (self.name, rev))
2556
Anthony King7bdac712014-07-16 12:56:40 +01002557 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002558 cmd = ['cherry-pick']
2559 cmd.append(rev)
2560 cmd.append('--')
2561 if GitCommand(self, cmd).Wait() != 0:
2562 if self._allrefs:
2563 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2564
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302565 def _LsRemote(self, refs):
2566 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302567 p = GitCommand(self, cmd, capture_stdout=True)
2568 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002569 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302570 return None
2571
Anthony King7bdac712014-07-16 12:56:40 +01002572 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002573 cmd = ['revert']
2574 cmd.append('--no-edit')
2575 cmd.append(rev)
2576 cmd.append('--')
2577 if GitCommand(self, cmd).Wait() != 0:
2578 if self._allrefs:
2579 raise GitError('%s revert %s ' % (self.name, rev))
2580
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002581 def _ResetHard(self, rev, quiet=True):
2582 cmd = ['reset', '--hard']
2583 if quiet:
2584 cmd.append('-q')
2585 cmd.append(rev)
2586 if GitCommand(self, cmd).Wait() != 0:
2587 raise GitError('%s reset --hard %s ' % (self.name, rev))
2588
Martin Kellye4e94d22017-03-21 16:05:12 -07002589 def _SyncSubmodules(self, quiet=True):
2590 cmd = ['submodule', 'update', '--init', '--recursive']
2591 if quiet:
2592 cmd.append('-q')
2593 if GitCommand(self, cmd).Wait() != 0:
2594 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2595
Anthony King7bdac712014-07-16 12:56:40 +01002596 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002597 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598 if onto is not None:
2599 cmd.extend(['--onto', onto])
2600 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002601 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602 raise GitError('%s rebase %s ' % (self.name, upstream))
2603
Pierre Tardy3d125942012-05-04 12:18:12 +02002604 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002605 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002606 if ffonly:
2607 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 if GitCommand(self, cmd).Wait() != 0:
2609 raise GitError('%s merge %s ' % (self.name, head))
2610
David Pursehousee8ace262020-02-13 12:41:15 +09002611 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002612 init_git_dir = not os.path.exists(self.gitdir)
2613 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002614 try:
2615 # Initialize the bare repository, which contains all of the objects.
2616 if init_obj_dir:
2617 os.makedirs(self.objdir)
2618 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002619
Kevin Degib1a07b82015-07-27 13:33:43 -06002620 # If we have a separate directory to hold refs, initialize it as well.
2621 if self.objdir != self.gitdir:
2622 if init_git_dir:
2623 os.makedirs(self.gitdir)
2624
2625 if init_obj_dir or init_git_dir:
2626 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2627 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002628 try:
2629 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2630 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002631 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002632 print("Retrying clone after deleting %s" %
2633 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002634 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002635 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2636 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002637 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002638 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002639 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2640 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002641 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002642 raise e
2643 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002644
Kevin Degi384b3c52014-10-16 16:02:58 -06002645 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002646 mp = self.manifest.manifestProject
2647 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002648
Kevin Degib1a07b82015-07-27 13:33:43 -06002649 if ref_dir or mirror_git:
2650 if not mirror_git:
2651 mirror_git = os.path.join(ref_dir, self.name + '.git')
2652 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2653 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002654
Kevin Degib1a07b82015-07-27 13:33:43 -06002655 if os.path.exists(mirror_git):
2656 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002657
Kevin Degib1a07b82015-07-27 13:33:43 -06002658 elif os.path.exists(repo_git):
2659 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002660
Kevin Degib1a07b82015-07-27 13:33:43 -06002661 else:
2662 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002663
Kevin Degib1a07b82015-07-27 13:33:43 -06002664 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002665 if not os.path.isabs(ref_dir):
2666 # The alternate directory is relative to the object database.
2667 ref_dir = os.path.relpath(ref_dir,
2668 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002669 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2670 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002671
David Pursehousee8ace262020-02-13 12:41:15 +09002672 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002673
2674 m = self.manifest.manifestProject.config
2675 for key in ['user.name', 'user.email']:
2676 if m.Has(key, include_defaults=False):
2677 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002678 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002679 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002680 if self.manifest.IsMirror:
2681 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002682 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002683 self.config.SetString('core.bare', None)
2684 except Exception:
2685 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002686 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002687 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002688 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002689 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002690
David Pursehousee8ace262020-02-13 12:41:15 +09002691 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002692 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002693 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002694
David Pursehousee8ace262020-02-13 12:41:15 +09002695 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002696 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002697 if not os.path.exists(hooks):
2698 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002699 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002700 name = os.path.basename(stock_hook)
2701
Victor Boivie65e0f352011-04-18 11:23:29 +02002702 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002703 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002704 # Don't install a Gerrit Code Review hook if this
2705 # project does not appear to use it for reviews.
2706 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002707 # Since the manifest project is one of those, but also
2708 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002709 continue
2710
2711 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002712 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002713 continue
2714 if os.path.exists(dst):
2715 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002716 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002717 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002718 if not quiet:
2719 _warn("%s: Not replacing locally modified %s hook",
2720 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002721 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002722 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002723 platform_utils.symlink(
2724 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002725 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002726 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002727 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002728 else:
2729 raise
2730
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002732 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002733 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002734 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002735 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002736 remote.review = self.remote.review
2737 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002739 if self.worktree:
2740 remote.ResetFetch(mirror=False)
2741 else:
2742 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002743 remote.Save()
2744
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002745 def _InitMRef(self):
2746 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002747 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002749 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002750 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002751
2752 def _InitAnyMRef(self, ref):
2753 cur = self.bare_ref.symref(ref)
2754
2755 if self.revisionId:
2756 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2757 msg = 'manifest set to %s' % self.revisionId
2758 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002759 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002760 else:
2761 remote = self.GetRemote(self.remote.name)
2762 dst = remote.ToLocal(self.revisionExpr)
2763 if cur != dst:
2764 msg = 'manifest set to %s' % self.revisionExpr
2765 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002766
Kevin Degi384b3c52014-10-16 16:02:58 -06002767 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002768 symlink_files = self.shareable_files[:]
2769 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002770 if share_refs:
2771 symlink_files += self.working_tree_files
2772 symlink_dirs += self.working_tree_dirs
2773 to_symlink = symlink_files + symlink_dirs
2774 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002775 # Try to self-heal a bit in simple cases.
2776 dst_path = os.path.join(destdir, name)
2777 src_path = os.path.join(srcdir, name)
2778
2779 if name in self.working_tree_dirs:
2780 # If the dir is missing under .repo/projects/, create it.
2781 if not os.path.exists(src_path):
2782 os.makedirs(src_path)
2783
2784 elif name in self.working_tree_files:
2785 # If it's a file under the checkout .git/ and the .repo/projects/ has
2786 # nothing, move the file under the .repo/projects/ tree.
2787 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2788 platform_utils.rename(dst_path, src_path)
2789
2790 # If the path exists under the .repo/projects/ and there's no symlink
2791 # under the checkout .git/, recreate the symlink.
2792 if name in self.working_tree_dirs or name in self.working_tree_files:
2793 if os.path.exists(src_path) and not os.path.exists(dst_path):
2794 platform_utils.symlink(
2795 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2796
2797 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002798 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002799 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002800 # Fail if the links are pointing to the wrong place
2801 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002802 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002803 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002804 'work tree. If you\'re comfortable with the '
2805 'possibility of losing the work tree\'s git metadata,'
2806 ' use `repo sync --force-sync {0}` to '
2807 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002808
David James8d201162013-10-11 17:03:19 -07002809 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2810 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2811
2812 Args:
2813 gitdir: The bare git repository. Must already be initialized.
2814 dotgit: The repository you would like to initialize.
2815 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2816 Only one work tree can store refs under a given |gitdir|.
2817 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2818 This saves you the effort of initializing |dotgit| yourself.
2819 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002820 symlink_files = self.shareable_files[:]
2821 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002822 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002823 symlink_files += self.working_tree_files
2824 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002825 to_symlink = symlink_files + symlink_dirs
2826
2827 to_copy = []
2828 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002829 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002830
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002831 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002832 for name in set(to_copy).union(to_symlink):
2833 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002834 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002835 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002836
Kevin Degi384b3c52014-10-16 16:02:58 -06002837 if os.path.lexists(dst):
2838 continue
David James8d201162013-10-11 17:03:19 -07002839
2840 # If the source dir doesn't exist, create an empty dir.
2841 if name in symlink_dirs and not os.path.lexists(src):
2842 os.makedirs(src)
2843
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002844 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002845 platform_utils.symlink(
2846 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002847 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002848 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002849 shutil.copytree(src, dst)
2850 elif os.path.isfile(src):
2851 shutil.copy(src, dst)
2852
Conley Owens80b87fe2014-05-09 17:13:44 -07002853 # If the source file doesn't exist, ensure the destination
2854 # file doesn't either.
2855 if name in symlink_files and not os.path.lexists(src):
2856 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002857 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002858 except OSError:
2859 pass
2860
David James8d201162013-10-11 17:03:19 -07002861 except OSError as e:
2862 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002863 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002864 else:
2865 raise
2866
Martin Kellye4e94d22017-03-21 16:05:12 -07002867 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002868 realdotgit = os.path.join(self.worktree, '.git')
2869 tmpdotgit = realdotgit + '.tmp'
2870 init_dotgit = not os.path.exists(realdotgit)
2871 if init_dotgit:
2872 dotgit = tmpdotgit
2873 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2874 os.makedirs(tmpdotgit)
2875 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2876 copy_all=False)
2877 else:
2878 dotgit = realdotgit
2879
Kevin Degib1a07b82015-07-27 13:33:43 -06002880 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002881 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2882 except GitError as e:
2883 if force_sync and not init_dotgit:
2884 try:
2885 platform_utils.rmtree(dotgit)
2886 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002887 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002888 raise e
2889 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
Mike Frysingerf4545122019-11-11 04:34:16 -05002891 if init_dotgit:
2892 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002893
Mike Frysingerf4545122019-11-11 04:34:16 -05002894 # Now that the .git dir is fully set up, move it to its final home.
2895 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002896
Mike Frysingerf4545122019-11-11 04:34:16 -05002897 # Finish checking out the worktree.
2898 cmd = ['read-tree', '--reset', '-u']
2899 cmd.append('-v')
2900 cmd.append(HEAD)
2901 if GitCommand(self, cmd).Wait() != 0:
2902 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002903
Mike Frysingerf4545122019-11-11 04:34:16 -05002904 if submodules:
2905 self._SyncSubmodules(quiet=True)
2906 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002907
Renaud Paquay788e9622017-01-27 11:41:12 -08002908 def _get_symlink_error_message(self):
2909 if platform_utils.isWindows():
2910 return ('Unable to create symbolic link. Please re-run the command as '
2911 'Administrator, or see '
2912 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2913 'for other options.')
2914 return 'filesystem must support symlinks'
2915
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002916 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002917 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002918
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002919 def _revlist(self, *args, **kw):
2920 a = []
2921 a.extend(args)
2922 a.append('--')
2923 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002924
2925 @property
2926 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002927 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002928
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002929 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002930 """Get logs between two revisions of this project."""
2931 comp = '..'
2932 if rev1:
2933 revs = [rev1]
2934 if rev2:
2935 revs.extend([comp, rev2])
2936 cmd = ['log', ''.join(revs)]
2937 out = DiffColoring(self.config)
2938 if out.is_on and color:
2939 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002940 if pretty_format is not None:
2941 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002942 if oneline:
2943 cmd.append('--oneline')
2944
2945 try:
2946 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2947 if log.Wait() == 0:
2948 return log.stdout
2949 except GitError:
2950 # worktree may not exist if groups changed for example. In that case,
2951 # try in gitdir instead.
2952 if not os.path.exists(self.worktree):
2953 return self.bare_git.log(*cmd[1:])
2954 else:
2955 raise
2956 return None
2957
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002958 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2959 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002960 """Get the list of logs from this revision to given revisionId"""
2961 logs = {}
2962 selfId = self.GetRevisionId(self._allrefs)
2963 toId = toProject.GetRevisionId(toProject._allrefs)
2964
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002965 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2966 pretty_format=pretty_format)
2967 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2968 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002969 return logs
2970
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002972
David James8d201162013-10-11 17:03:19 -07002973 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 self._project = project
2975 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002976 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002977
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002978 def LsOthers(self):
2979 p = GitCommand(self._project,
2980 ['ls-files',
2981 '-z',
2982 '--others',
2983 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002984 bare=False,
David James8d201162013-10-11 17:03:19 -07002985 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002986 capture_stdout=True,
2987 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 if p.Wait() == 0:
2989 out = p.stdout
2990 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002991 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002992 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002993 return []
2994
2995 def DiffZ(self, name, *args):
2996 cmd = [name]
2997 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002998 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002999 cmd.extend(args)
3000 p = GitCommand(self._project,
3001 cmd,
David James8d201162013-10-11 17:03:19 -07003002 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003003 bare=False,
3004 capture_stdout=True,
3005 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003006 try:
3007 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003008 if not hasattr(out, 'encode'):
3009 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003010 r = {}
3011 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003012 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003013 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003014 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003015 info = next(out)
3016 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003017 except StopIteration:
3018 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003019
3020 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003021
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003022 def __init__(self, path, omode, nmode, oid, nid, state):
3023 self.path = path
3024 self.src_path = None
3025 self.old_mode = omode
3026 self.new_mode = nmode
3027 self.old_id = oid
3028 self.new_id = nid
3029
3030 if len(state) == 1:
3031 self.status = state
3032 self.level = None
3033 else:
3034 self.status = state[:1]
3035 self.level = state[1:]
3036 while self.level.startswith('0'):
3037 self.level = self.level[1:]
3038
3039 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003040 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003041 if info.status in ('R', 'C'):
3042 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003043 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003044 r[info.path] = info
3045 return r
3046 finally:
3047 p.Wait()
3048
3049 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003050 if self._bare:
3051 path = os.path.join(self._project.gitdir, HEAD)
3052 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003053 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003054 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003055 with open(path) as fd:
3056 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003057 except IOError as e:
3058 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003059 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303060 line = line.decode()
3061 except AttributeError:
3062 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003063 if line.startswith('ref: '):
3064 return line[5:-1]
3065 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003066
3067 def SetHead(self, ref, message=None):
3068 cmdv = []
3069 if message is not None:
3070 cmdv.extend(['-m', message])
3071 cmdv.append(HEAD)
3072 cmdv.append(ref)
3073 self.symbolic_ref(*cmdv)
3074
3075 def DetachHead(self, new, message=None):
3076 cmdv = ['--no-deref']
3077 if message is not None:
3078 cmdv.extend(['-m', message])
3079 cmdv.append(HEAD)
3080 cmdv.append(new)
3081 self.update_ref(*cmdv)
3082
3083 def UpdateRef(self, name, new, old=None,
3084 message=None,
3085 detach=False):
3086 cmdv = []
3087 if message is not None:
3088 cmdv.extend(['-m', message])
3089 if detach:
3090 cmdv.append('--no-deref')
3091 cmdv.append(name)
3092 cmdv.append(new)
3093 if old is not None:
3094 cmdv.append(old)
3095 self.update_ref(*cmdv)
3096
3097 def DeleteRef(self, name, old=None):
3098 if not old:
3099 old = self.rev_parse(name)
3100 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003101 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003102
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003103 def rev_list(self, *args, **kw):
3104 if 'format' in kw:
3105 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3106 else:
3107 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003108 cmdv.extend(args)
3109 p = GitCommand(self._project,
3110 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003111 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003112 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003113 capture_stdout=True,
3114 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003115 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003116 raise GitError('%s rev-list %s: %s' %
3117 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003118 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003119
3120 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003121 """Allow arbitrary git commands using pythonic syntax.
3122
3123 This allows you to do things like:
3124 git_obj.rev_parse('HEAD')
3125
3126 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3127 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003128 Any other positional arguments will be passed to the git command, and the
3129 following keyword arguments are supported:
3130 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003131
3132 Args:
3133 name: The name of the git command to call. Any '_' characters will
3134 be replaced with '-'.
3135
3136 Returns:
3137 A callable object that will try to call git with the named command.
3138 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003139 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003140
Dave Borowitz091f8932012-10-23 17:01:04 -07003141 def runner(*args, **kwargs):
3142 cmdv = []
3143 config = kwargs.pop('config', None)
3144 for k in kwargs:
3145 raise TypeError('%s() got an unexpected keyword argument %r'
3146 % (name, k))
3147 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303148 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003149 cmdv.append('-c')
3150 cmdv.append('%s=%s' % (k, v))
3151 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003152 cmdv.extend(args)
3153 p = GitCommand(self._project,
3154 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003155 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003156 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003157 capture_stdout=True,
3158 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003159 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003160 raise GitError('%s %s: %s' %
3161 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003162 r = p.stdout
3163 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3164 return r[:-1]
3165 return r
3166 return runner
3167
3168
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003169class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003170
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003171 def __str__(self):
3172 return 'prior sync failed; rebase still in progress'
3173
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177 def __str__(self):
3178 return 'contains uncommitted changes'
3179
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003180
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003181class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003182
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003183 def __init__(self, project, text):
3184 self.project = project
3185 self.text = text
3186
3187 def Print(self, syncbuf):
3188 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3189 syncbuf.out.nl()
3190
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003191
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003192class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003193
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003194 def __init__(self, project, why):
3195 self.project = project
3196 self.why = why
3197
3198 def Print(self, syncbuf):
3199 syncbuf.out.fail('error: %s/: %s',
3200 self.project.relpath,
3201 str(self.why))
3202 syncbuf.out.nl()
3203
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003204
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003205class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003206
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003207 def __init__(self, project, action):
3208 self.project = project
3209 self.action = action
3210
3211 def Run(self, syncbuf):
3212 out = syncbuf.out
3213 out.project('project %s/', self.project.relpath)
3214 out.nl()
3215 try:
3216 self.action()
3217 out.nl()
3218 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003219 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003220 out.nl()
3221 return False
3222
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003223
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003224class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003225
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003226 def __init__(self, config):
3227 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003228 self.project = self.printer('header', attr='bold')
3229 self.info = self.printer('info')
3230 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003231
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003232
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003233class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003234
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235 def __init__(self, config, detach_head=False):
3236 self._messages = []
3237 self._failures = []
3238 self._later_queue1 = []
3239 self._later_queue2 = []
3240
3241 self.out = _SyncColoring(config)
3242 self.out.redirect(sys.stderr)
3243
3244 self.detach_head = detach_head
3245 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003246 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003247
3248 def info(self, project, fmt, *args):
3249 self._messages.append(_InfoMessage(project, fmt % args))
3250
3251 def fail(self, project, err=None):
3252 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003253 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003254
3255 def later1(self, project, what):
3256 self._later_queue1.append(_Later(project, what))
3257
3258 def later2(self, project, what):
3259 self._later_queue2.append(_Later(project, what))
3260
3261 def Finish(self):
3262 self._PrintMessages()
3263 self._RunLater()
3264 self._PrintMessages()
3265 return self.clean
3266
David Rileye0684ad2017-04-05 00:02:59 -07003267 def Recently(self):
3268 recent_clean = self.recent_clean
3269 self.recent_clean = True
3270 return recent_clean
3271
3272 def _MarkUnclean(self):
3273 self.clean = False
3274 self.recent_clean = False
3275
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003276 def _RunLater(self):
3277 for q in ['_later_queue1', '_later_queue2']:
3278 if not self._RunQueue(q):
3279 return
3280
3281 def _RunQueue(self, queue):
3282 for m in getattr(self, queue):
3283 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003284 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003285 return False
3286 setattr(self, queue, [])
3287 return True
3288
3289 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003290 if self._messages or self._failures:
3291 if os.isatty(2):
3292 self.out.write(progress.CSI_ERASE_LINE)
3293 self.out.write('\r')
3294
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003295 for m in self._messages:
3296 m.Print(self)
3297 for m in self._failures:
3298 m.Print(self)
3299
3300 self._messages = []
3301 self._failures = []
3302
3303
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003304class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003305
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003306 """A special project housed under .repo.
3307 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003308
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003309 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003310 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003311 manifest=manifest,
3312 name=name,
3313 gitdir=gitdir,
3314 objdir=gitdir,
3315 worktree=worktree,
3316 remote=RemoteSpec('origin'),
3317 relpath='.repo/%s' % name,
3318 revisionExpr='refs/heads/master',
3319 revisionId=None,
3320 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003321
3322 def PreSync(self):
3323 if self.Exists:
3324 cb = self.CurrentBranch
3325 if cb:
3326 base = self.GetBranch(cb).merge
3327 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003328 self.revisionExpr = base
3329 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003330
Martin Kelly224a31a2017-07-10 14:46:25 -07003331 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003332 """ Prepare MetaProject for manifest branch switch
3333 """
3334
3335 # detach and delete manifest branch, allowing a new
3336 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003337 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003338 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003339 syncbuf.Finish()
3340
3341 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003342 ['update-ref', '-d', 'refs/heads/default'],
3343 capture_stdout=True,
3344 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003345
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003346 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003347 def LastFetch(self):
3348 try:
3349 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3350 return os.path.getmtime(fh)
3351 except OSError:
3352 return 0
3353
3354 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003355 def HasChanges(self):
3356 """Has the remote received new commits not yet checked out?
3357 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003358 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003359 return False
3360
David Pursehouse8a68ff92012-09-24 12:15:13 +09003361 all_refs = self.bare_ref.all
3362 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003363 head = self.work_git.GetHead()
3364 if head.startswith(R_HEADS):
3365 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003366 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003367 except KeyError:
3368 head = None
3369
3370 if revid == head:
3371 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003372 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003373 return True
3374 return False