blob: bfe4a3404b66e55710697007a70a3304b8bf02ce [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
201 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000202 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200203 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700204 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200206 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800207 validate_certs=True,
208 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800209 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700210 people,
Brian Harring435370c2012-07-28 15:37:04 -0700211 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000212 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200213 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700214 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200215 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200216 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800217 validate_certs=validate_certs,
218 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700220 def GetPublishedRefs(self):
221 refs = {}
222 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223 self.branch.remote.SshReviewUrl(self.project.UserEmail),
224 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700225 for line in output.split('\n'):
226 try:
227 (sha, ref) = line.split()
228 refs[sha] = ref
229 except ValueError:
230 pass
231
232 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 def __init__(self, config):
238 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100239 self.project = self.printer('header', attr='bold')
240 self.branch = self.printer('header', attr='bold')
241 self.nobranch = self.printer('nobranch', fg='red')
242 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243
Anthony King7bdac712014-07-16 12:56:40 +0100244 self.added = self.printer('added', fg='green')
245 self.changed = self.printer('changed', fg='red')
246 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
248
249class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700250
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 def __init__(self, config):
252 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100253 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400254 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
Anthony King7bdac712014-07-16 12:56:40 +0100257class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700258
James W. Mills24c13082012-04-12 15:04:13 -0500259 def __init__(self, name, value, keep):
260 self.name = name
261 self.value = value
262 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Mike Frysingere6a202f2019-08-02 15:57:57 -0400265def _SafeExpandPath(base, subpath, skipfinal=False):
266 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700267
Mike Frysingere6a202f2019-08-02 15:57:57 -0400268 We make sure no intermediate symlinks are traversed, and that the final path
269 is not a special file (e.g. not a socket or fifo).
270
271 NB: We rely on a number of paths already being filtered out while parsing the
272 manifest. See the validation logic in manifest_xml.py for more details.
273 """
274 components = subpath.split(os.path.sep)
275 if skipfinal:
276 # Whether the caller handles the final component itself.
277 finalpart = components.pop()
278
279 path = base
280 for part in components:
281 if part in {'.', '..'}:
282 raise ManifestInvalidPathError(
283 '%s: "%s" not allowed in paths' % (subpath, part))
284
285 path = os.path.join(path, part)
286 if platform_utils.islink(path):
287 raise ManifestInvalidPathError(
288 '%s: traversing symlinks not allow' % (path,))
289
290 if os.path.exists(path):
291 if not os.path.isfile(path) and not platform_utils.isdir(path):
292 raise ManifestInvalidPathError(
293 '%s: only regular files & directories allowed' % (path,))
294
295 if skipfinal:
296 path = os.path.join(path, finalpart)
297
298 return path
299
300
301class _CopyFile(object):
302 """Container for <copyfile> manifest element."""
303
304 def __init__(self, git_worktree, src, topdir, dest):
305 """Register a <copyfile> request.
306
307 Args:
308 git_worktree: Absolute path to the git project checkout.
309 src: Relative path under |git_worktree| of file to read.
310 topdir: Absolute path to the top of the repo client checkout.
311 dest: Relative path under |topdir| of file to write.
312 """
313 self.git_worktree = git_worktree
314 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700315 self.src = src
316 self.dest = dest
317
318 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400319 src = _SafeExpandPath(self.git_worktree, self.src)
320 dest = _SafeExpandPath(self.topdir, self.dest)
321
322 if platform_utils.isdir(src):
323 raise ManifestInvalidPathError(
324 '%s: copying from directory not supported' % (self.src,))
325 if platform_utils.isdir(dest):
326 raise ManifestInvalidPathError(
327 '%s: copying to directory not allowed' % (self.dest,))
328
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700329 # copy file if it does not exist or is out of date
330 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
331 try:
332 # remove existing file first, since it might be read-only
333 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800334 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400335 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200336 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700337 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200338 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339 shutil.copy(src, dest)
340 # make the file read-only
341 mode = os.stat(dest)[stat.ST_MODE]
342 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
343 os.chmod(dest, mode)
344 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700345 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700346
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700347
Anthony King7bdac712014-07-16 12:56:40 +0100348class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400349 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700350
Mike Frysingere6a202f2019-08-02 15:57:57 -0400351 def __init__(self, git_worktree, src, topdir, dest):
352 """Register a <linkfile> request.
353
354 Args:
355 git_worktree: Absolute path to the git project checkout.
356 src: Target of symlink relative to path under |git_worktree|.
357 topdir: Absolute path to the top of the repo client checkout.
358 dest: Relative path under |topdir| of symlink to create.
359 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700360 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400361 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500362 self.src = src
363 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500364
Wink Saville4c426ef2015-06-03 08:05:17 -0700365 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700367 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368 try:
369 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800370 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800371 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500372 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700373 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700374 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700376 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700378 _error('Cannot link file %s to %s', relSrc, absDest)
379
380 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400381 """Link the self.src & self.dest paths.
382
383 Handles wild cards on the src linking all of the files in the source in to
384 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700385 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500386 # Some people use src="." to create stable links to projects. Lets allow
387 # that but reject all other uses of "." to keep things simple.
388 if self.src == '.':
389 src = self.git_worktree
390 else:
391 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400392
393 if os.path.exists(src):
394 # Entity exists so just a simple one to one link operation.
395 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
396 # dest & src are absolute paths at this point. Make sure the target of
397 # the symlink is relative in the context of the repo client checkout.
398 relpath = os.path.relpath(src, os.path.dirname(dest))
399 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700400 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400401 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 if os.path.exists(dest) and not platform_utils.isdir(dest):
404 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700405 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400406 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700407 # Create a releative path from source dir to destination dir
408 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400409 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700410
411 # Get the source file name
412 srcFile = os.path.basename(absSrcFile)
413
414 # Now form the final full paths to srcFile. They will be
415 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400416 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700417 relSrc = os.path.join(relSrcDir, srcFile)
418 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500419
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700420
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700421class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700422
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700423 def __init__(self,
424 name,
Anthony King7bdac712014-07-16 12:56:40 +0100425 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700426 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100427 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700428 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700429 orig_name=None,
430 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700431 self.name = name
432 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700433 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700434 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100435 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700436 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700437 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700438
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700439
Doug Anderson37282b42011-03-04 11:54:18 -0800440class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700441
Doug Anderson37282b42011-03-04 11:54:18 -0800442 """A RepoHook contains information about a script to run as a hook.
443
444 Hooks are used to run a python script before running an upload (for instance,
445 to run presubmit checks). Eventually, we may have hooks for other actions.
446
447 This shouldn't be confused with files in the 'repo/hooks' directory. Those
448 files are copied into each '.git/hooks' folder for each project. Repo-level
449 hooks are associated instead with repo actions.
450
451 Hooks are always python. When a hook is run, we will load the hook into the
452 interpreter and execute its main() function.
453 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700454
Doug Anderson37282b42011-03-04 11:54:18 -0800455 def __init__(self,
456 hook_type,
457 hooks_project,
458 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400459 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800460 abort_if_user_denies=False):
461 """RepoHook constructor.
462
463 Params:
464 hook_type: A string representing the type of hook. This is also used
465 to figure out the name of the file containing the hook. For
466 example: 'pre-upload'.
467 hooks_project: The project containing the repo hooks. If you have a
468 manifest, this is manifest.repo_hooks_project. OK if this is None,
469 which will make the hook a no-op.
470 topdir: Repo's top directory (the one containing the .repo directory).
471 Scripts will run with CWD as this directory. If you have a manifest,
472 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400473 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800474 abort_if_user_denies: If True, we'll throw a HookError() if the user
475 doesn't allow us to run the hook.
476 """
477 self._hook_type = hook_type
478 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400479 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800480 self._topdir = topdir
481 self._abort_if_user_denies = abort_if_user_denies
482
483 # Store the full path to the script for convenience.
484 if self._hooks_project:
485 self._script_fullpath = os.path.join(self._hooks_project.worktree,
486 self._hook_type + '.py')
487 else:
488 self._script_fullpath = None
489
490 def _GetHash(self):
491 """Return a hash of the contents of the hooks directory.
492
493 We'll just use git to do this. This hash has the property that if anything
494 changes in the directory we will return a different has.
495
496 SECURITY CONSIDERATION:
497 This hash only represents the contents of files in the hook directory, not
498 any other files imported or called by hooks. Changes to imported files
499 can change the script behavior without affecting the hash.
500
501 Returns:
502 A string representing the hash. This will always be ASCII so that it can
503 be printed to the user easily.
504 """
505 assert self._hooks_project, "Must have hooks to calculate their hash."
506
507 # We will use the work_git object rather than just calling GetRevisionId().
508 # That gives us a hash of the latest checked in version of the files that
509 # the user will actually be executing. Specifically, GetRevisionId()
510 # doesn't appear to change even if a user checks out a different version
511 # of the hooks repo (via git checkout) nor if a user commits their own revs.
512 #
513 # NOTE: Local (non-committed) changes will not be factored into this hash.
514 # I think this is OK, since we're really only worried about warning the user
515 # about upstream changes.
516 return self._hooks_project.work_git.rev_parse('HEAD')
517
518 def _GetMustVerb(self):
519 """Return 'must' if the hook is required; 'should' if not."""
520 if self._abort_if_user_denies:
521 return 'must'
522 else:
523 return 'should'
524
525 def _CheckForHookApproval(self):
526 """Check to see whether this hook has been approved.
527
Mike Frysinger40252c22016-08-15 21:23:44 -0400528 We'll accept approval of manifest URLs if they're using secure transports.
529 This way the user can say they trust the manifest hoster. For insecure
530 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800531
532 Note that we ask permission for each individual hook even though we use
533 the hash of all hooks when detecting changes. We'd like the user to be
534 able to approve / deny each hook individually. We only use the hash of all
535 hooks because there is no other easy way to detect changes to local imports.
536
537 Returns:
538 True if this hook is approved to run; False otherwise.
539
540 Raises:
541 HookError: Raised if the user doesn't approve and abort_if_user_denies
542 was passed to the consturctor.
543 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400544 if self._ManifestUrlHasSecureScheme():
545 return self._CheckForHookApprovalManifest()
546 else:
547 return self._CheckForHookApprovalHash()
548
549 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
550 changed_prompt):
551 """Check for approval for a particular attribute and hook.
552
553 Args:
554 subkey: The git config key under [repo.hooks.<hook_type>] to store the
555 last approved string.
556 new_val: The new value to compare against the last approved one.
557 main_prompt: Message to display to the user to ask for approval.
558 changed_prompt: Message explaining why we're re-asking for approval.
559
560 Returns:
561 True if this hook is approved to run; False otherwise.
562
563 Raises:
564 HookError: Raised if the user doesn't approve and abort_if_user_denies
565 was passed to the consturctor.
566 """
Doug Anderson37282b42011-03-04 11:54:18 -0800567 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400568 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800569
Mike Frysinger40252c22016-08-15 21:23:44 -0400570 # Get the last value that the user approved for this hook; may be None.
571 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800572
Mike Frysinger40252c22016-08-15 21:23:44 -0400573 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800574 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400575 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800576 # Approval matched. We're done.
577 return True
578 else:
579 # Give the user a reason why we're prompting, since they last told
580 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400581 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800582 else:
583 prompt = ''
584
585 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
586 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400587 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530588 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900589 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800590
591 # User is doing a one-time approval.
592 if response in ('y', 'yes'):
593 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400594 elif response == 'always':
595 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800596 return True
597
598 # For anything else, we'll assume no approval.
599 if self._abort_if_user_denies:
600 raise HookError('You must allow the %s hook or use --no-verify.' %
601 self._hook_type)
602
603 return False
604
Mike Frysinger40252c22016-08-15 21:23:44 -0400605 def _ManifestUrlHasSecureScheme(self):
606 """Check if the URI for the manifest is a secure transport."""
607 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
608 parse_results = urllib.parse.urlparse(self._manifest_url)
609 return parse_results.scheme in secure_schemes
610
611 def _CheckForHookApprovalManifest(self):
612 """Check whether the user has approved this manifest host.
613
614 Returns:
615 True if this hook is approved to run; False otherwise.
616 """
617 return self._CheckForHookApprovalHelper(
618 'approvedmanifest',
619 self._manifest_url,
620 'Run hook scripts from %s' % (self._manifest_url,),
621 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
622
623 def _CheckForHookApprovalHash(self):
624 """Check whether the user has approved the hooks repo.
625
626 Returns:
627 True if this hook is approved to run; False otherwise.
628 """
629 prompt = ('Repo %s run the script:\n'
630 ' %s\n'
631 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700632 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400633 return self._CheckForHookApprovalHelper(
634 'approvedhash',
635 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700636 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400637 'Scripts have changed since %s was allowed.' % (self._hook_type,))
638
Mike Frysingerf7c51602019-06-18 17:23:39 -0400639 @staticmethod
640 def _ExtractInterpFromShebang(data):
641 """Extract the interpreter used in the shebang.
642
643 Try to locate the interpreter the script is using (ignoring `env`).
644
645 Args:
646 data: The file content of the script.
647
648 Returns:
649 The basename of the main script interpreter, or None if a shebang is not
650 used or could not be parsed out.
651 """
652 firstline = data.splitlines()[:1]
653 if not firstline:
654 return None
655
656 # The format here can be tricky.
657 shebang = firstline[0].strip()
658 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
659 if not m:
660 return None
661
662 # If the using `env`, find the target program.
663 interp = m.group(1)
664 if os.path.basename(interp) == 'env':
665 interp = m.group(2)
666
667 return interp
668
669 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
670 """Execute the hook script through |interp|.
671
672 Note: Support for this feature should be dropped ~Jun 2021.
673
674 Args:
675 interp: The Python program to run.
676 context: Basic Python context to execute the hook inside.
677 kwargs: Arbitrary arguments to pass to the hook script.
678
679 Raises:
680 HookError: When the hooks failed for any reason.
681 """
682 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
683 script = """
684import json, os, sys
685path = '''%(path)s'''
686kwargs = json.loads('''%(kwargs)s''')
687context = json.loads('''%(context)s''')
688sys.path.insert(0, os.path.dirname(path))
689data = open(path).read()
690exec(compile(data, path, 'exec'), context)
691context['main'](**kwargs)
692""" % {
693 'path': self._script_fullpath,
694 'kwargs': json.dumps(kwargs),
695 'context': json.dumps(context),
696 }
697
698 # We pass the script via stdin to avoid OS argv limits. It also makes
699 # unhandled exception tracebacks less verbose/confusing for users.
700 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
701 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
702 proc.communicate(input=script.encode('utf-8'))
703 if proc.returncode:
704 raise HookError('Failed to run %s hook.' % (self._hook_type,))
705
706 def _ExecuteHookViaImport(self, data, context, **kwargs):
707 """Execute the hook code in |data| directly.
708
709 Args:
710 data: The code of the hook to execute.
711 context: Basic Python context to execute the hook inside.
712 kwargs: Arbitrary arguments to pass to the hook script.
713
714 Raises:
715 HookError: When the hooks failed for any reason.
716 """
717 # Exec, storing global context in the context dict. We catch exceptions
718 # and convert to a HookError w/ just the failing traceback.
719 try:
720 exec(compile(data, self._script_fullpath, 'exec'), context)
721 except Exception:
722 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
723 (traceback.format_exc(), self._hook_type))
724
725 # Running the script should have defined a main() function.
726 if 'main' not in context:
727 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
728
729 # Call the main function in the hook. If the hook should cause the
730 # build to fail, it will raise an Exception. We'll catch that convert
731 # to a HookError w/ just the failing traceback.
732 try:
733 context['main'](**kwargs)
734 except Exception:
735 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
736 'above.' % (traceback.format_exc(), self._hook_type))
737
Doug Anderson37282b42011-03-04 11:54:18 -0800738 def _ExecuteHook(self, **kwargs):
739 """Actually execute the given hook.
740
741 This will run the hook's 'main' function in our python interpreter.
742
743 Args:
744 kwargs: Keyword arguments to pass to the hook. These are often specific
745 to the hook type. For instance, pre-upload hooks will contain
746 a project_list.
747 """
748 # Keep sys.path and CWD stashed away so that we can always restore them
749 # upon function exit.
750 orig_path = os.getcwd()
751 orig_syspath = sys.path
752
753 try:
754 # Always run hooks with CWD as topdir.
755 os.chdir(self._topdir)
756
757 # Put the hook dir as the first item of sys.path so hooks can do
758 # relative imports. We want to replace the repo dir as [0] so
759 # hooks can't import repo files.
760 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
761
Mike Frysingerf7c51602019-06-18 17:23:39 -0400762 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500763 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800764
Doug Anderson37282b42011-03-04 11:54:18 -0800765 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
766 # We don't actually want hooks to define their main with this argument--
767 # it's there to remind them that their hook should always take **kwargs.
768 # For instance, a pre-upload hook should be defined like:
769 # def main(project_list, **kwargs):
770 #
771 # This allows us to later expand the API without breaking old hooks.
772 kwargs = kwargs.copy()
773 kwargs['hook_should_take_kwargs'] = True
774
Mike Frysingerf7c51602019-06-18 17:23:39 -0400775 # See what version of python the hook has been written against.
776 data = open(self._script_fullpath).read()
777 interp = self._ExtractInterpFromShebang(data)
778 reexec = False
779 if interp:
780 prog = os.path.basename(interp)
781 if prog.startswith('python2') and sys.version_info.major != 2:
782 reexec = True
783 elif prog.startswith('python3') and sys.version_info.major == 2:
784 reexec = True
785
786 # Attempt to execute the hooks through the requested version of Python.
787 if reexec:
788 try:
789 self._ExecuteHookViaReexec(interp, context, **kwargs)
790 except OSError as e:
791 if e.errno == errno.ENOENT:
792 # We couldn't find the interpreter, so fallback to importing.
793 reexec = False
794 else:
795 raise
796
797 # Run the hook by importing directly.
798 if not reexec:
799 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800800 finally:
801 # Restore sys.path and CWD.
802 sys.path = orig_syspath
803 os.chdir(orig_path)
804
805 def Run(self, user_allows_all_hooks, **kwargs):
806 """Run the hook.
807
808 If the hook doesn't exist (because there is no hooks project or because
809 this particular hook is not enabled), this is a no-op.
810
811 Args:
812 user_allows_all_hooks: If True, we will never prompt about running the
813 hook--we'll just assume it's OK to run it.
814 kwargs: Keyword arguments to pass to the hook. These are often specific
815 to the hook type. For instance, pre-upload hooks will contain
816 a project_list.
817
818 Raises:
819 HookError: If there was a problem finding the hook or the user declined
820 to run a required hook (from _CheckForHookApproval).
821 """
822 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700823 if ((not self._hooks_project) or (self._hook_type not in
824 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800825 return
826
827 # Bail with a nice error if we can't find the hook.
828 if not os.path.isfile(self._script_fullpath):
829 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
830
831 # Make sure the user is OK with running the hook.
832 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
833 return
834
835 # Run the hook with the same version of python we're using.
836 self._ExecuteHook(**kwargs)
837
838
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600840 # These objects can be shared between several working trees.
841 shareable_files = ['description', 'info']
842 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
843 # These objects can only be used by a single working tree.
844 working_tree_files = ['config', 'packed-refs', 'shallow']
845 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700846
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 def __init__(self,
848 manifest,
849 name,
850 remote,
851 gitdir,
David James8d201162013-10-11 17:03:19 -0700852 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 worktree,
854 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700855 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800856 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100857 rebase=True,
858 groups=None,
859 sync_c=False,
860 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900861 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100862 clone_depth=None,
863 upstream=None,
864 parent=None,
865 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900866 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700867 optimized_fetch=False,
868 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800869 """Init a Project object.
870
871 Args:
872 manifest: The XmlManifest object.
873 name: The `name` attribute of manifest.xml's project element.
874 remote: RemoteSpec object specifying its remote's properties.
875 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700876 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800877 worktree: Absolute path of git working tree.
878 relpath: Relative path of git working tree to repo's top directory.
879 revisionExpr: The `revision` attribute of manifest.xml's project element.
880 revisionId: git commit id for checking out.
881 rebase: The `rebase` attribute of manifest.xml's project element.
882 groups: The `groups` attribute of manifest.xml's project element.
883 sync_c: The `sync-c` attribute of manifest.xml's project element.
884 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900885 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800886 upstream: The `upstream` attribute of manifest.xml's project element.
887 parent: The parent Project object.
888 is_derived: False if the project was explicitly defined in the manifest;
889 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400890 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900891 optimized_fetch: If True, when a project is set to a sha1 revision, only
892 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700893 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800894 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 self.manifest = manifest
896 self.name = name
897 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800898 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700899 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800900 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700901 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800902 else:
903 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700905 self.revisionExpr = revisionExpr
906
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700907 if revisionId is None \
908 and revisionExpr \
909 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700910 self.revisionId = revisionExpr
911 else:
912 self.revisionId = revisionId
913
Mike Pontillod3153822012-02-28 11:53:24 -0800914 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700915 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700916 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800917 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900918 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900919 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700920 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800921 self.parent = parent
922 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900923 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800924 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800925
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500928 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500929 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700930 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
931 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800933 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700934 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800935 else:
936 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700937 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700938 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700939 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400940 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700941 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
Doug Anderson37282b42011-03-04 11:54:18 -0800943 # This will be filled in if a project is later identified to be the
944 # project containing repo hooks.
945 self.enabled_repo_hooks = []
946
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800948 def Derived(self):
949 return self.is_derived
950
951 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700953 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
955 @property
956 def CurrentBranch(self):
957 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400958
959 The branch name omits the 'refs/heads/' prefix.
960 None is returned if the project is on a detached HEAD, or if the work_git is
961 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400963 try:
964 b = self.work_git.GetHead()
965 except NoManifestException:
966 # If the local checkout is in a bad state, don't barf. Let the callers
967 # process this like the head is unreadable.
968 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if b.startswith(R_HEADS):
970 return b[len(R_HEADS):]
971 return None
972
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700973 def IsRebaseInProgress(self):
974 w = self.worktree
975 g = os.path.join(w, '.git')
976 return os.path.exists(os.path.join(g, 'rebase-apply')) \
977 or os.path.exists(os.path.join(g, 'rebase-merge')) \
978 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200979
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 def IsDirty(self, consider_untracked=True):
981 """Is the working directory modified in some way?
982 """
983 self.work_git.update_index('-q',
984 '--unmerged',
985 '--ignore-missing',
986 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900987 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 return True
989 if self.work_git.DiffZ('diff-files'):
990 return True
991 if consider_untracked and self.work_git.LsOthers():
992 return True
993 return False
994
995 _userident_name = None
996 _userident_email = None
997
998 @property
999 def UserName(self):
1000 """Obtain the user's personal name.
1001 """
1002 if self._userident_name is None:
1003 self._LoadUserIdentity()
1004 return self._userident_name
1005
1006 @property
1007 def UserEmail(self):
1008 """Obtain the user's email address. This is very likely
1009 to be their Gerrit login.
1010 """
1011 if self._userident_email is None:
1012 self._LoadUserIdentity()
1013 return self._userident_email
1014
1015 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001016 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1017 m = re.compile("^(.*) <([^>]*)> ").match(u)
1018 if m:
1019 self._userident_name = m.group(1)
1020 self._userident_email = m.group(2)
1021 else:
1022 self._userident_name = ''
1023 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024
1025 def GetRemote(self, name):
1026 """Get the configuration for a single remote.
1027 """
1028 return self.config.GetRemote(name)
1029
1030 def GetBranch(self, name):
1031 """Get the configuration for a single branch.
1032 """
1033 return self.config.GetBranch(name)
1034
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001035 def GetBranches(self):
1036 """Get all existing local branches.
1037 """
1038 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001039 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001040 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001041
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301042 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001043 if name.startswith(R_HEADS):
1044 name = name[len(R_HEADS):]
1045 b = self.GetBranch(name)
1046 b.current = name == current
1047 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001049 heads[name] = b
1050
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301051 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001052 if name.startswith(R_PUB):
1053 name = name[len(R_PUB):]
1054 b = heads.get(name)
1055 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001056 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001057
1058 return heads
1059
Colin Cross5acde752012-03-28 20:15:45 -07001060 def MatchesGroups(self, manifest_groups):
1061 """Returns true if the manifest groups specified at init should cause
1062 this project to be synced.
1063 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001064 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001065
1066 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001067 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001068 manifest_groups: "-group1,group2"
1069 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001070
1071 The special manifest group "default" will match any project that
1072 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001073 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001074 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001075 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001076 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001077 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001078
Conley Owens971de8e2012-04-16 10:36:08 -07001079 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001080 for group in expanded_manifest_groups:
1081 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001082 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001083 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001084 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001085
Conley Owens971de8e2012-04-16 10:36:08 -07001086 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001088# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001089 def UncommitedFiles(self, get_all=True):
1090 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001092 Args:
1093 get_all: a boolean, if True - get information about all different
1094 uncommitted files. If False - return as soon as any kind of
1095 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001096 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001097 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001098 self.work_git.update_index('-q',
1099 '--unmerged',
1100 '--ignore-missing',
1101 '--refresh')
1102 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001103 details.append("rebase in progress")
1104 if not get_all:
1105 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001106
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1108 if changes:
1109 details.extend(changes)
1110 if not get_all:
1111 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001112
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001113 changes = self.work_git.DiffZ('diff-files').keys()
1114 if changes:
1115 details.extend(changes)
1116 if not get_all:
1117 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001118
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001119 changes = self.work_git.LsOthers()
1120 if changes:
1121 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001122
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001123 return details
1124
1125 def HasChanges(self):
1126 """Returns true if there are uncommitted changes.
1127 """
1128 if self.UncommitedFiles(get_all=False):
1129 return True
1130 else:
1131 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001132
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001133 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001135
1136 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001137 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001138 quiet: If True then only print the project name. Do not print
1139 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001141 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001142 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001143 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001144 print(file=output_redir)
1145 print('project %s/' % self.relpath, file=output_redir)
1146 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 return
1148
1149 self.work_git.update_index('-q',
1150 '--unmerged',
1151 '--ignore-missing',
1152 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001153 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1155 df = self.work_git.DiffZ('diff-files')
1156 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001157 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001158 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159
1160 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001161 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001162 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001163 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001165 if quiet:
1166 out.nl()
1167 return 'DIRTY'
1168
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 branch = self.CurrentBranch
1170 if branch is None:
1171 out.nobranch('(*** NO BRANCH ***)')
1172 else:
1173 out.branch('branch %s', branch)
1174 out.nl()
1175
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001176 if rb:
1177 out.important('prior sync failed; rebase still in progress')
1178 out.nl()
1179
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001180 paths = list()
1181 paths.extend(di.keys())
1182 paths.extend(df.keys())
1183 paths.extend(do)
1184
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301185 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001186 try:
1187 i = di[p]
1188 except KeyError:
1189 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001191 try:
1192 f = df[p]
1193 except KeyError:
1194 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001195
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001196 if i:
1197 i_status = i.status.upper()
1198 else:
1199 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001201 if f:
1202 f_status = f.status.lower()
1203 else:
1204 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205
1206 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001207 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001208 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209 else:
1210 line = ' %s%s\t%s' % (i_status, f_status, p)
1211
1212 if i and not f:
1213 out.added('%s', line)
1214 elif (i and f) or (not i and f):
1215 out.changed('%s', line)
1216 elif not i and not f:
1217 out.untracked('%s', line)
1218 else:
1219 out.write('%s', line)
1220 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001221
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001222 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223
pelyad67872d2012-03-28 14:49:58 +03001224 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225 """Prints the status of the repository to stdout.
1226 """
1227 out = DiffColoring(self.config)
1228 cmd = ['diff']
1229 if out.is_on:
1230 cmd.append('--color')
1231 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001232 if absolute_paths:
1233 cmd.append('--src-prefix=a/%s/' % self.relpath)
1234 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001236 try:
1237 p = GitCommand(self,
1238 cmd,
1239 capture_stdout=True,
1240 capture_stderr=True)
1241 except GitError as e:
1242 out.nl()
1243 out.project('project %s/' % self.relpath)
1244 out.nl()
1245 out.fail('%s', str(e))
1246 out.nl()
1247 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248 has_diff = False
1249 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001250 if not hasattr(line, 'encode'):
1251 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252 if not has_diff:
1253 out.nl()
1254 out.project('project %s/' % self.relpath)
1255 out.nl()
1256 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001257 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001258 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001260# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001261 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 """Was the branch published (uploaded) for code review?
1263 If so, returns the SHA-1 hash of the last published
1264 state for the branch.
1265 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001266 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001267 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001268 try:
1269 return self.bare_git.rev_parse(key)
1270 except GitError:
1271 return None
1272 else:
1273 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001274 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001275 except KeyError:
1276 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277
David Pursehouse8a68ff92012-09-24 12:15:13 +09001278 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 """Prunes any stale published refs.
1280 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001281 if all_refs is None:
1282 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283 heads = set()
1284 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301285 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 if name.startswith(R_HEADS):
1287 heads.add(name)
1288 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001289 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301291 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 n = name[len(R_PUB):]
1293 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001294 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001296 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 """List any branches which can be uploaded for review.
1298 """
1299 heads = {}
1300 pubed = {}
1301
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301302 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001306 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307
1308 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301309 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001312 if selected_branch and branch != selected_branch:
1313 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001315 rb = self.GetUploadableBranch(branch)
1316 if rb:
1317 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 return ready
1319
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001320 def GetUploadableBranch(self, branch_name):
1321 """Get a single uploadable branch, or None.
1322 """
1323 branch = self.GetBranch(branch_name)
1324 base = branch.LocalMerge
1325 if branch.LocalMerge:
1326 rb = ReviewableBranch(self, branch, base)
1327 if rb.commits:
1328 return rb
1329 return None
1330
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001331 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001332 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001333 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001334 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001335 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001336 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001337 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001338 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001339 validate_certs=True,
1340 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 """Uploads the named branch for code review.
1342 """
1343 if branch is None:
1344 branch = self.CurrentBranch
1345 if branch is None:
1346 raise GitError('not currently on a branch')
1347
1348 branch = self.GetBranch(branch)
1349 if not branch.LocalMerge:
1350 raise GitError('branch %s does not track a remote' % branch.name)
1351 if not branch.remote.review:
1352 raise GitError('remote %s has no review url' % branch.remote.name)
1353
Bryan Jacobsf609f912013-05-06 13:36:24 -04001354 if dest_branch is None:
1355 dest_branch = self.dest_branch
1356 if dest_branch is None:
1357 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358 if not dest_branch.startswith(R_HEADS):
1359 dest_branch = R_HEADS + dest_branch
1360
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001361 if not branch.remote.projectname:
1362 branch.remote.projectname = self.name
1363 branch.remote.Save()
1364
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001365 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001366 if url is None:
1367 raise UploadError('review not configured')
1368 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001369
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001370 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001371 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001372
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001373 for push_option in (push_options or []):
1374 cmd.append('-o')
1375 cmd.append(push_option)
1376
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001377 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001378
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001379 if dest_branch.startswith(R_HEADS):
1380 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001381
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001382 upload_type = 'for'
1383 if draft:
1384 upload_type = 'drafts'
1385
1386 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1387 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001388 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001389 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001390 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001391
David Pursehousef25a3702018-11-14 19:01:22 -08001392 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001393 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001394 if notify:
1395 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001396 if private:
1397 opts += ['private']
1398 if wip:
1399 opts += ['wip']
1400 if opts:
1401 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001402 cmd.append(ref_spec)
1403
Anthony King7bdac712014-07-16 12:56:40 +01001404 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001405 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406
1407 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1408 self.bare_git.UpdateRef(R_PUB + branch.name,
1409 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001410 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001412# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001413 def _ExtractArchive(self, tarpath, path=None):
1414 """Extract the given tar on its current location
1415
1416 Args:
1417 - tarpath: The path to the actual tar file
1418
1419 """
1420 try:
1421 with tarfile.open(tarpath, 'r') as tar:
1422 tar.extractall(path=path)
1423 return True
1424 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001425 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001426 return False
1427
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001428 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001429 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001430 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001431 is_new=None,
1432 current_branch_only=False,
1433 force_sync=False,
1434 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001435 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001436 archive=False,
1437 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001438 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001439 submodules=False,
1440 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441 """Perform only the network IO portion of the sync process.
1442 Local working directory/branch state is not affected.
1443 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001444 if archive and not isinstance(self, MetaProject):
1445 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001446 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001447 return False
1448
1449 name = self.relpath.replace('\\', '/')
1450 name = name.replace('/', '_')
1451 tarpath = '%s.tar' % name
1452 topdir = self.manifest.topdir
1453
1454 try:
1455 self._FetchArchive(tarpath, cwd=topdir)
1456 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001457 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001458 return False
1459
1460 # From now on, we only need absolute tarpath
1461 tarpath = os.path.join(topdir, tarpath)
1462
1463 if not self._ExtractArchive(tarpath, path=topdir):
1464 return False
1465 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001466 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001467 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001468 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001469 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001470 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001471 if is_new is None:
1472 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001473 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001474 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001475 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001476 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001477 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001478
1479 if is_new:
1480 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1481 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001482 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001483 # This works for both absolute and relative alternate directories.
1484 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001485 except IOError:
1486 alt_dir = None
1487 else:
1488 alt_dir = None
1489
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001490 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001491 and alt_dir is None \
1492 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493 is_new = False
1494
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001495 if not current_branch_only:
1496 if self.sync_c:
1497 current_branch_only = True
1498 elif not self.manifest._loaded:
1499 # Manifest cannot check defaults until it syncs.
1500 current_branch_only = False
1501 elif self.manifest.default.sync_c:
1502 current_branch_only = True
1503
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001504 if not self.sync_tags:
1505 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001506
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001507 if self.clone_depth:
1508 depth = self.clone_depth
1509 else:
1510 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1511
Mike Frysinger521d01b2020-02-17 01:51:49 -05001512 # See if we can skip the network fetch entirely.
1513 if not (optimized_fetch and
1514 (ID_RE.match(self.revisionExpr) and
1515 self._CheckForImmutableRevision())):
1516 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001517 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1518 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001519 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001520 submodules=submodules, force_sync=force_sync,
1521 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001522 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001523
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001524 mp = self.manifest.manifestProject
1525 dissociate = mp.config.GetBoolean('repo.dissociate')
1526 if dissociate:
1527 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1528 if os.path.exists(alternates_file):
1529 cmd = ['repack', '-a', '-d']
1530 if GitCommand(self, cmd, bare=True).Wait() != 0:
1531 return False
1532 platform_utils.remove(alternates_file)
1533
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001534 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001535 self._InitMRef()
1536 else:
1537 self._InitMirrorHead()
1538 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001539 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001540 except OSError:
1541 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001542 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001543
1544 def PostRepoUpgrade(self):
1545 self._InitHooks()
1546
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001547 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001548 if self.manifest.isGitcClient:
1549 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001550 for copyfile in self.copyfiles:
1551 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001552 for linkfile in self.linkfiles:
1553 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554
Julien Camperguedd654222014-01-09 16:21:37 +01001555 def GetCommitRevisionId(self):
1556 """Get revisionId of a commit.
1557
1558 Use this method instead of GetRevisionId to get the id of the commit rather
1559 than the id of the current git object (for example, a tag)
1560
1561 """
1562 if not self.revisionExpr.startswith(R_TAGS):
1563 return self.GetRevisionId(self._allrefs)
1564
1565 try:
1566 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1567 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001568 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1569 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001570
David Pursehouse8a68ff92012-09-24 12:15:13 +09001571 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001572 if self.revisionId:
1573 return self.revisionId
1574
1575 rem = self.GetRemote(self.remote.name)
1576 rev = rem.ToLocal(self.revisionExpr)
1577
David Pursehouse8a68ff92012-09-24 12:15:13 +09001578 if all_refs is not None and rev in all_refs:
1579 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001580
1581 try:
1582 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1583 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001584 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1585 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001586
Martin Kellye4e94d22017-03-21 16:05:12 -07001587 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001588 """Perform only the local IO portion of the sync process.
1589 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001591 if not os.path.exists(self.gitdir):
1592 syncbuf.fail(self,
1593 'Cannot checkout %s due to missing network sync; Run '
1594 '`repo sync -n %s` first.' %
1595 (self.name, self.name))
1596 return
1597
Martin Kellye4e94d22017-03-21 16:05:12 -07001598 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001599 all_refs = self.bare_ref.all
1600 self.CleanPublishedCache(all_refs)
1601 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001602
David Pursehouse1d947b32012-10-25 12:23:11 +09001603 def _doff():
1604 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001605 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001606
Martin Kellye4e94d22017-03-21 16:05:12 -07001607 def _dosubmodules():
1608 self._SyncSubmodules(quiet=True)
1609
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001610 head = self.work_git.GetHead()
1611 if head.startswith(R_HEADS):
1612 branch = head[len(R_HEADS):]
1613 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001614 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001615 except KeyError:
1616 head = None
1617 else:
1618 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001619
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001620 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001621 # Currently on a detached HEAD. The user is assumed to
1622 # not have any local modifications worth worrying about.
1623 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001624 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001625 syncbuf.fail(self, _PriorSyncFailedError())
1626 return
1627
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001628 if head == revid:
1629 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001630 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001631 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001632 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001633 # The copy/linkfile config may have changed.
1634 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001635 return
1636 else:
1637 lost = self._revlist(not_rev(revid), HEAD)
1638 if lost:
1639 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001640
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001642 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001643 if submodules:
1644 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001645 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001646 syncbuf.fail(self, e)
1647 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001648 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001649 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001651 if head == revid:
1652 # No changes; don't do anything further.
1653 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001654 # The copy/linkfile config may have changed.
1655 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001656 return
1657
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001658 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001660 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001662 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001664 syncbuf.info(self,
1665 "leaving %s; does not track upstream",
1666 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001668 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001669 if submodules:
1670 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001671 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001672 syncbuf.fail(self, e)
1673 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001674 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001675 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001677 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001678
1679 # See if we can perform a fast forward merge. This can happen if our
1680 # branch isn't in the exact same state as we last published.
1681 try:
1682 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1683 # Skip the published logic.
1684 pub = False
1685 except GitError:
1686 pub = self.WasPublished(branch.name, all_refs)
1687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001689 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690 if not_merged:
1691 if upstream_gain:
1692 # The user has published this branch and some of those
1693 # commits are not yet merged upstream. We do not want
1694 # to rewrite the published commits so we punt.
1695 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001696 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001697 "branch %s is published (but not merged) and is now "
1698 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001699 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001700 elif pub == head:
1701 # All published commits are merged, and thus we are a
1702 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001703 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001704 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001705 if submodules:
1706 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001707 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001709 # Examine the local commits not in the remote. Find the
1710 # last one attributed to this user, if any.
1711 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001712 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001713 last_mine = None
1714 cnt_mine = 0
1715 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001716 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001717 if committer_email == self.UserEmail:
1718 last_mine = commit_id
1719 cnt_mine += 1
1720
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001721 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001722 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723
1724 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001725 syncbuf.fail(self, _DirtyError())
1726 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001728 # If the upstream switched on us, warn the user.
1729 #
1730 if branch.merge != self.revisionExpr:
1731 if branch.merge and self.revisionExpr:
1732 syncbuf.info(self,
1733 'manifest switched %s...%s',
1734 branch.merge,
1735 self.revisionExpr)
1736 elif branch.merge:
1737 syncbuf.info(self,
1738 'manifest no longer tracks %s',
1739 branch.merge)
1740
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001741 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001743 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001744 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001745 syncbuf.info(self,
1746 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001747 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001749 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001750 if not ID_RE.match(self.revisionExpr):
1751 # in case of manifest sync the revisionExpr might be a SHA1
1752 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001753 if not branch.merge.startswith('refs/'):
1754 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755 branch.Save()
1756
Mike Pontillod3153822012-02-28 11:53:24 -08001757 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001758 def _docopyandlink():
1759 self._CopyAndLinkFiles()
1760
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001761 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001762 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001763 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001764 if submodules:
1765 syncbuf.later2(self, _dosubmodules)
1766 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001767 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001769 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001770 if submodules:
1771 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001772 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001773 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001774 syncbuf.fail(self, e)
1775 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001776 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001777 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001778 if submodules:
1779 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780
Mike Frysingere6a202f2019-08-02 15:57:57 -04001781 def AddCopyFile(self, src, dest, topdir):
1782 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783
Mike Frysingere6a202f2019-08-02 15:57:57 -04001784 No filesystem changes occur here. Actual copying happens later on.
1785
1786 Paths should have basic validation run on them before being queued.
1787 Further checking will be handled when the actual copy happens.
1788 """
1789 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1790
1791 def AddLinkFile(self, src, dest, topdir):
1792 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1793
1794 No filesystem changes occur here. Actual linking happens later on.
1795
1796 Paths should have basic validation run on them before being queued.
1797 Further checking will be handled when the actual link happens.
1798 """
1799 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001800
James W. Mills24c13082012-04-12 15:04:13 -05001801 def AddAnnotation(self, name, value, keep):
1802 self.annotations.append(_Annotation(name, value, keep))
1803
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001804 def DownloadPatchSet(self, change_id, patch_id):
1805 """Download a single patch set of a single change to FETCH_HEAD.
1806 """
1807 remote = self.GetRemote(self.remote.name)
1808
1809 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001810 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001811 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001812 if GitCommand(self, cmd, bare=True).Wait() != 0:
1813 return None
1814 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001815 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001816 change_id,
1817 patch_id,
1818 self.bare_git.rev_parse('FETCH_HEAD'))
1819
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001820# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001821 def GetHeadPath(self):
1822 """Return the full path to the HEAD ref."""
1823 dotgit = os.path.join(self.worktree, '.git')
1824 if os.path.isfile(dotgit):
1825 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1826 with open(dotgit) as fp:
1827 setting = fp.read()
1828 assert setting.startswith('gitdir:')
1829 gitdir = setting.split(':', 1)[1].strip()
1830 dotgit = os.path.join(self.worktree, gitdir)
1831 return os.path.join(dotgit, HEAD)
1832
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001833 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834 """Create a new branch off the manifest's revision.
1835 """
Simran Basib9a1b732015-08-20 12:19:28 -07001836 if not branch_merge:
1837 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001838 head = self.work_git.GetHead()
1839 if head == (R_HEADS + name):
1840 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841
David Pursehouse8a68ff92012-09-24 12:15:13 +09001842 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001843 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001844 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001845 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001846 capture_stdout=True,
1847 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001848
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001849 branch = self.GetBranch(name)
1850 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001851 branch.merge = branch_merge
1852 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1853 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001854
1855 if revision is None:
1856 revid = self.GetRevisionId(all_refs)
1857 else:
1858 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001859
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001860 if head.startswith(R_HEADS):
1861 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001862 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001863 except KeyError:
1864 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001865 if revid and head and revid == head:
1866 ref = os.path.join(self.gitdir, R_HEADS + name)
1867 try:
1868 os.makedirs(os.path.dirname(ref))
1869 except OSError:
1870 pass
1871 _lwrite(ref, '%s\n' % revid)
Mike Frysingerf914edc2020-02-09 03:01:56 -05001872 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001873 branch.Save()
1874 return True
1875
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001876 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001877 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001878 capture_stdout=True,
1879 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001880 branch.Save()
1881 return True
1882 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001883
Wink Saville02d79452009-04-10 13:01:24 -07001884 def CheckoutBranch(self, name):
1885 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001886
1887 Args:
1888 name: The name of the branch to checkout.
1889
1890 Returns:
1891 True if the checkout succeeded; False if it didn't; None if the branch
1892 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001893 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001894 rev = R_HEADS + name
1895 head = self.work_git.GetHead()
1896 if head == rev:
1897 # Already on the branch
1898 #
1899 return True
Wink Saville02d79452009-04-10 13:01:24 -07001900
David Pursehouse8a68ff92012-09-24 12:15:13 +09001901 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001902 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001903 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001904 except KeyError:
1905 # Branch does not exist in this project
1906 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001907 return None
Wink Saville02d79452009-04-10 13:01:24 -07001908
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001909 if head.startswith(R_HEADS):
1910 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001911 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001912 except KeyError:
1913 head = None
1914
1915 if head == revid:
1916 # Same revision; just update HEAD to point to the new
1917 # target branch, but otherwise take no other action.
1918 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001919 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001920 return True
1921
1922 return GitCommand(self,
1923 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001924 capture_stdout=True,
1925 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001926
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001927 def AbandonBranch(self, name):
1928 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001929
1930 Args:
1931 name: The name of the branch to abandon.
1932
1933 Returns:
1934 True if the abandon succeeded; False if it didn't; None if the branch
1935 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001936 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001937 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001938 all_refs = self.bare_ref.all
1939 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001940 # Doesn't exist
1941 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001942
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001943 head = self.work_git.GetHead()
1944 if head == rev:
1945 # We can't destroy the branch while we are sitting
1946 # on it. Switch to a detached HEAD.
1947 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001948 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001949
David Pursehouse8a68ff92012-09-24 12:15:13 +09001950 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001951 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05001952 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001953 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001954 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001955
1956 return GitCommand(self,
1957 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001958 capture_stdout=True,
1959 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001960
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001961 def PruneHeads(self):
1962 """Prune any topic branches already merged into upstream.
1963 """
1964 cb = self.CurrentBranch
1965 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001966 left = self._allrefs
1967 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001968 if name.startswith(R_HEADS):
1969 name = name[len(R_HEADS):]
1970 if cb is None or name != cb:
1971 kill.append(name)
1972
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001973 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001974 if cb is not None \
1975 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001976 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001977 self.work_git.DetachHead(HEAD)
1978 kill.append(cb)
1979
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001980 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001981 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001982
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001983 try:
1984 self.bare_git.DetachHead(rev)
1985
1986 b = ['branch', '-d']
1987 b.extend(kill)
1988 b = GitCommand(self, b, bare=True,
1989 capture_stdout=True,
1990 capture_stderr=True)
1991 b.Wait()
1992 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001993 if ID_RE.match(old):
1994 self.bare_git.DetachHead(old)
1995 else:
1996 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001997 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001998
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001999 for branch in kill:
2000 if (R_HEADS + branch) not in left:
2001 self.CleanPublishedCache()
2002 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003
2004 if cb and cb not in kill:
2005 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002006 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
2008 kept = []
2009 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002010 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002011 branch = self.GetBranch(branch)
2012 base = branch.LocalMerge
2013 if not base:
2014 base = rev
2015 kept.append(ReviewableBranch(self, branch, base))
2016 return kept
2017
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002018# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002019 def GetRegisteredSubprojects(self):
2020 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002021
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002022 def rec(subprojects):
2023 if not subprojects:
2024 return
2025 result.extend(subprojects)
2026 for p in subprojects:
2027 rec(p.subprojects)
2028 rec(self.subprojects)
2029 return result
2030
2031 def _GetSubmodules(self):
2032 # Unfortunately we cannot call `git submodule status --recursive` here
2033 # because the working tree might not exist yet, and it cannot be used
2034 # without a working tree in its current implementation.
2035
2036 def get_submodules(gitdir, rev):
2037 # Parse .gitmodules for submodule sub_paths and sub_urls
2038 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2039 if not sub_paths:
2040 return []
2041 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2042 # revision of submodule repository
2043 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2044 submodules = []
2045 for sub_path, sub_url in zip(sub_paths, sub_urls):
2046 try:
2047 sub_rev = sub_revs[sub_path]
2048 except KeyError:
2049 # Ignore non-exist submodules
2050 continue
2051 submodules.append((sub_rev, sub_path, sub_url))
2052 return submodules
2053
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002054 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2055 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002056
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002057 def parse_gitmodules(gitdir, rev):
2058 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2059 try:
Anthony King7bdac712014-07-16 12:56:40 +01002060 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2061 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002062 except GitError:
2063 return [], []
2064 if p.Wait() != 0:
2065 return [], []
2066
2067 gitmodules_lines = []
2068 fd, temp_gitmodules_path = tempfile.mkstemp()
2069 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002070 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002071 os.close(fd)
2072 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002073 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2074 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002075 if p.Wait() != 0:
2076 return [], []
2077 gitmodules_lines = p.stdout.split('\n')
2078 except GitError:
2079 return [], []
2080 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002081 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002082
2083 names = set()
2084 paths = {}
2085 urls = {}
2086 for line in gitmodules_lines:
2087 if not line:
2088 continue
2089 m = re_path.match(line)
2090 if m:
2091 names.add(m.group(1))
2092 paths[m.group(1)] = m.group(2)
2093 continue
2094 m = re_url.match(line)
2095 if m:
2096 names.add(m.group(1))
2097 urls[m.group(1)] = m.group(2)
2098 continue
2099 names = sorted(names)
2100 return ([paths.get(name, '') for name in names],
2101 [urls.get(name, '') for name in names])
2102
2103 def git_ls_tree(gitdir, rev, paths):
2104 cmd = ['ls-tree', rev, '--']
2105 cmd.extend(paths)
2106 try:
Anthony King7bdac712014-07-16 12:56:40 +01002107 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2108 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002109 except GitError:
2110 return []
2111 if p.Wait() != 0:
2112 return []
2113 objects = {}
2114 for line in p.stdout.split('\n'):
2115 if not line.strip():
2116 continue
2117 object_rev, object_path = line.split()[2:4]
2118 objects[object_path] = object_rev
2119 return objects
2120
2121 try:
2122 rev = self.GetRevisionId()
2123 except GitError:
2124 return []
2125 return get_submodules(self.gitdir, rev)
2126
2127 def GetDerivedSubprojects(self):
2128 result = []
2129 if not self.Exists:
2130 # If git repo does not exist yet, querying its submodules will
2131 # mess up its states; so return here.
2132 return result
2133 for rev, path, url in self._GetSubmodules():
2134 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002135 relpath, worktree, gitdir, objdir = \
2136 self.manifest.GetSubprojectPaths(self, name, path)
2137 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002138 if project:
2139 result.extend(project.GetDerivedSubprojects())
2140 continue
David James8d201162013-10-11 17:03:19 -07002141
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002142 if url.startswith('..'):
2143 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002144 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002145 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002146 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002147 review=self.remote.review,
2148 revision=self.remote.revision)
2149 subproject = Project(manifest=self.manifest,
2150 name=name,
2151 remote=remote,
2152 gitdir=gitdir,
2153 objdir=objdir,
2154 worktree=worktree,
2155 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002156 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002157 revisionId=rev,
2158 rebase=self.rebase,
2159 groups=self.groups,
2160 sync_c=self.sync_c,
2161 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002162 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002163 parent=self,
2164 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002165 result.append(subproject)
2166 result.extend(subproject.GetDerivedSubprojects())
2167 return result
2168
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002169# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002170 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002171 try:
2172 # if revision (sha or tag) is not present then following function
2173 # throws an error.
2174 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2175 return True
2176 except GitError:
2177 # There is no such persistent revision. We have to fetch it.
2178 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002179
Julien Campergue335f5ef2013-10-16 11:02:35 +02002180 def _FetchArchive(self, tarpath, cwd=None):
2181 cmd = ['archive', '-v', '-o', tarpath]
2182 cmd.append('--remote=%s' % self.remote.url)
2183 cmd.append('--prefix=%s/' % self.relpath)
2184 cmd.append(self.revisionExpr)
2185
2186 command = GitCommand(self, cmd, cwd=cwd,
2187 capture_stdout=True,
2188 capture_stderr=True)
2189
2190 if command.Wait() != 0:
2191 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2192
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002193 def _RemoteFetch(self, name=None,
2194 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002195 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002196 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002197 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002198 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002199 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002200 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002201 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002202 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002203 force_sync=False,
2204 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002205
2206 is_sha1 = False
2207 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002208 # The depth should not be used when fetching to a mirror because
2209 # it will result in a shallow repository that cannot be cloned or
2210 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002211 # The repo project should also never be synced with partial depth.
2212 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2213 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002214
Shawn Pearce69e04d82014-01-29 12:48:54 -08002215 if depth:
2216 current_branch_only = True
2217
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002218 if ID_RE.match(self.revisionExpr) is not None:
2219 is_sha1 = True
2220
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002221 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002222 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002223 # this is a tag and its sha1 value should never change
2224 tag_name = self.revisionExpr[len(R_TAGS):]
2225
2226 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002227 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002228 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002229 print('Skipped fetching project %s (already have persistent ref)'
2230 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002231 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002232 if is_sha1 and not depth:
2233 # When syncing a specific commit and --depth is not set:
2234 # * if upstream is explicitly specified and is not a sha1, fetch only
2235 # upstream as users expect only upstream to be fetch.
2236 # Note: The commit might not be in upstream in which case the sync
2237 # will fail.
2238 # * otherwise, fetch all branches to make sure we end up with the
2239 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002240 if self.upstream:
2241 current_branch_only = not ID_RE.match(self.upstream)
2242 else:
2243 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002244
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002245 if not name:
2246 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002247
2248 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002249 remote = self.GetRemote(name)
2250 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002251 ssh_proxy = True
2252
Shawn O. Pearce88443382010-10-08 10:02:09 +02002253 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002254 if alt_dir and 'objects' == os.path.basename(alt_dir):
2255 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002256 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2257 remote = self.GetRemote(name)
2258
David Pursehouse8a68ff92012-09-24 12:15:13 +09002259 all_refs = self.bare_ref.all
2260 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002261 tmp = set()
2262
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302263 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002264 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002265 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002266 all_refs[r] = ref_id
2267 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002268 continue
2269
David Pursehouse8a68ff92012-09-24 12:15:13 +09002270 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002271 continue
2272
David Pursehouse8a68ff92012-09-24 12:15:13 +09002273 r = 'refs/_alt/%s' % ref_id
2274 all_refs[r] = ref_id
2275 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002276 tmp.add(r)
2277
heping3d7bbc92017-04-12 19:51:47 +08002278 tmp_packed_lines = []
2279 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002280
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302281 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002282 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002283 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002284 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002285 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002286
heping3d7bbc92017-04-12 19:51:47 +08002287 tmp_packed = ''.join(tmp_packed_lines)
2288 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002289 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002290 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002291 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002292
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002293 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002294
Xin Li745be2e2019-06-03 11:24:30 -07002295 if clone_filter:
2296 git_require((2, 19, 0), fail=True, msg='partial clones')
2297 cmd.append('--filter=%s' % clone_filter)
2298 self.config.SetString('extensions.partialclone', self.remote.name)
2299
Conley Owensf97e8382015-01-21 11:12:46 -08002300 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002301 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002302 else:
2303 # If this repo has shallow objects, then we don't know which refs have
2304 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2305 # do this with projects that don't have shallow objects, since it is less
2306 # efficient.
2307 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2308 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002309
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002310 if quiet:
2311 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002312 if not self.worktree:
2313 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002314 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002315
Mike Frysingere57f1142019-03-18 21:27:54 -04002316 if force_sync:
2317 cmd.append('--force')
2318
David Pursehouse74cfd272015-10-14 10:50:15 +09002319 if prune:
2320 cmd.append('--prune')
2321
Martin Kellye4e94d22017-03-21 16:05:12 -07002322 if submodules:
2323 cmd.append('--recurse-submodules=on-demand')
2324
Kuang-che Wu6856f982019-11-25 12:37:55 +08002325 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002326 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002327 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002328 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002329 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002330 spec.append('tag')
2331 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002332
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302333 if self.manifest.IsMirror and not current_branch_only:
2334 branch = None
2335 else:
2336 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002337 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002338 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002339 # Shallow checkout of a specific commit, fetch from that commit and not
2340 # the heads only as the commit might be deeper in the history.
2341 spec.append(branch)
2342 else:
2343 if is_sha1:
2344 branch = self.upstream
2345 if branch is not None and branch.strip():
2346 if not branch.startswith('refs/'):
2347 branch = R_HEADS + branch
2348 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2349
2350 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2351 # whole repo.
2352 if self.manifest.IsMirror and not spec:
2353 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2354
2355 # If using depth then we should not get all the tags since they may
2356 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002357 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002358 cmd.append('--no-tags')
2359 else:
2360 cmd.append('--tags')
2361 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2362
Conley Owens80b87fe2014-05-09 17:13:44 -07002363 cmd.extend(spec)
2364
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002365 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002366 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002367 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2368 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002369 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002370 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002371 ok = True
2372 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002373 # If needed, run the 'git remote prune' the first time through the loop
2374 elif (not _i and
2375 "error:" in gitcmd.stderr and
2376 "git remote prune" in gitcmd.stderr):
2377 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002378 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002379 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002380 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002381 break
2382 continue
Brian Harring14a66742012-09-28 20:21:57 -07002383 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002384 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2385 # in sha1 mode, we just tried sync'ing from the upstream field; it
2386 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002387 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002388 elif ret < 0:
2389 # Git died with a signal, exit immediately
2390 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002391 if not verbose:
2392 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002393 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002394
2395 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002396 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002397 if old_packed != '':
2398 _lwrite(packed_refs, old_packed)
2399 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002400 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002401 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002402
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002403 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002404 # We just synced the upstream given branch; verify we
2405 # got what we wanted, else trigger a second run of all
2406 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002407 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002408 # Sync the current branch only with depth set to None.
2409 # We always pass depth=None down to avoid infinite recursion.
2410 return self._RemoteFetch(
2411 name=name, quiet=quiet, verbose=verbose,
2412 current_branch_only=current_branch_only and depth,
2413 initial=False, alt_dir=alt_dir,
2414 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002415
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002416 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002417
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002418 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002419 if initial and \
2420 (self.manifest.manifestProject.config.GetString('repo.depth') or
2421 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002422 return False
2423
2424 remote = self.GetRemote(self.remote.name)
2425 bundle_url = remote.url + '/clone.bundle'
2426 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002427 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2428 'persistent-http',
2429 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002430 return False
2431
2432 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2433 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2434
2435 exist_dst = os.path.exists(bundle_dst)
2436 exist_tmp = os.path.exists(bundle_tmp)
2437
2438 if not initial and not exist_dst and not exist_tmp:
2439 return False
2440
2441 if not exist_dst:
2442 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2443 if not exist_dst:
2444 return False
2445
2446 cmd = ['fetch']
2447 if quiet:
2448 cmd.append('--quiet')
2449 if not self.worktree:
2450 cmd.append('--update-head-ok')
2451 cmd.append(bundle_dst)
2452 for f in remote.fetch:
2453 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002454 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002455
2456 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002457 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002458 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002459 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002460 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002461 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002463 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002464 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002465 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002466
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002467 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002468 if quiet:
2469 cmd += ['--silent']
2470 if os.path.exists(tmpPath):
2471 size = os.stat(tmpPath).st_size
2472 if size >= 1024:
2473 cmd += ['--continue-at', '%d' % (size,)]
2474 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002475 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002476 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002477 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002478 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002479 if proxy:
2480 cmd += ['--proxy', proxy]
2481 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2482 cmd += ['--proxy', os.environ['http_proxy']]
2483 if srcUrl.startswith('persistent-https'):
2484 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2485 elif srcUrl.startswith('persistent-http'):
2486 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002487 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002488
Dave Borowitz137d0132015-01-02 11:12:54 -08002489 if IsTrace():
2490 Trace('%s', ' '.join(cmd))
2491 try:
2492 proc = subprocess.Popen(cmd)
2493 except OSError:
2494 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002495
Dave Borowitz137d0132015-01-02 11:12:54 -08002496 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002497
Dave Borowitz137d0132015-01-02 11:12:54 -08002498 if curlret == 22:
2499 # From curl man page:
2500 # 22: HTTP page not retrieved. The requested url was not found or
2501 # returned another error with the HTTP error code being 400 or above.
2502 # This return code only appears if -f, --fail is used.
2503 if not quiet:
2504 print("Server does not provide clone.bundle; ignoring.",
2505 file=sys.stderr)
2506 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002507
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002508 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002509 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002510 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002511 return True
2512 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002513 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002514 return False
2515 else:
2516 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002517
Kris Giesingc8d882a2014-12-23 13:02:32 -08002518 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002519 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002520 with open(path, 'rb') as f:
2521 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002522 return True
2523 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002524 if not quiet:
2525 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002526 return False
2527 except OSError:
2528 return False
2529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002530 def _Checkout(self, rev, quiet=False):
2531 cmd = ['checkout']
2532 if quiet:
2533 cmd.append('-q')
2534 cmd.append(rev)
2535 cmd.append('--')
2536 if GitCommand(self, cmd).Wait() != 0:
2537 if self._allrefs:
2538 raise GitError('%s checkout %s ' % (self.name, rev))
2539
Anthony King7bdac712014-07-16 12:56:40 +01002540 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002541 cmd = ['cherry-pick']
2542 cmd.append(rev)
2543 cmd.append('--')
2544 if GitCommand(self, cmd).Wait() != 0:
2545 if self._allrefs:
2546 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2547
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302548 def _LsRemote(self, refs):
2549 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302550 p = GitCommand(self, cmd, capture_stdout=True)
2551 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002552 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302553 return None
2554
Anthony King7bdac712014-07-16 12:56:40 +01002555 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002556 cmd = ['revert']
2557 cmd.append('--no-edit')
2558 cmd.append(rev)
2559 cmd.append('--')
2560 if GitCommand(self, cmd).Wait() != 0:
2561 if self._allrefs:
2562 raise GitError('%s revert %s ' % (self.name, rev))
2563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002564 def _ResetHard(self, rev, quiet=True):
2565 cmd = ['reset', '--hard']
2566 if quiet:
2567 cmd.append('-q')
2568 cmd.append(rev)
2569 if GitCommand(self, cmd).Wait() != 0:
2570 raise GitError('%s reset --hard %s ' % (self.name, rev))
2571
Martin Kellye4e94d22017-03-21 16:05:12 -07002572 def _SyncSubmodules(self, quiet=True):
2573 cmd = ['submodule', 'update', '--init', '--recursive']
2574 if quiet:
2575 cmd.append('-q')
2576 if GitCommand(self, cmd).Wait() != 0:
2577 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2578
Anthony King7bdac712014-07-16 12:56:40 +01002579 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002580 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002581 if onto is not None:
2582 cmd.extend(['--onto', onto])
2583 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002584 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002585 raise GitError('%s rebase %s ' % (self.name, upstream))
2586
Pierre Tardy3d125942012-05-04 12:18:12 +02002587 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002588 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002589 if ffonly:
2590 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002591 if GitCommand(self, cmd).Wait() != 0:
2592 raise GitError('%s merge %s ' % (self.name, head))
2593
David Pursehousee8ace262020-02-13 12:41:15 +09002594 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002595 init_git_dir = not os.path.exists(self.gitdir)
2596 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002597 try:
2598 # Initialize the bare repository, which contains all of the objects.
2599 if init_obj_dir:
2600 os.makedirs(self.objdir)
2601 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002602
Kevin Degib1a07b82015-07-27 13:33:43 -06002603 # If we have a separate directory to hold refs, initialize it as well.
2604 if self.objdir != self.gitdir:
2605 if init_git_dir:
2606 os.makedirs(self.gitdir)
2607
2608 if init_obj_dir or init_git_dir:
2609 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2610 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002611 try:
2612 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2613 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002614 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002615 print("Retrying clone after deleting %s" %
2616 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002617 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002618 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2619 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002620 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002621 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002622 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2623 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002624 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002625 raise e
2626 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002627
Kevin Degi384b3c52014-10-16 16:02:58 -06002628 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002629 mp = self.manifest.manifestProject
2630 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002631
Kevin Degib1a07b82015-07-27 13:33:43 -06002632 if ref_dir or mirror_git:
2633 if not mirror_git:
2634 mirror_git = os.path.join(ref_dir, self.name + '.git')
2635 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2636 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002637
Kevin Degib1a07b82015-07-27 13:33:43 -06002638 if os.path.exists(mirror_git):
2639 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002640
Kevin Degib1a07b82015-07-27 13:33:43 -06002641 elif os.path.exists(repo_git):
2642 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002643
Kevin Degib1a07b82015-07-27 13:33:43 -06002644 else:
2645 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002646
Kevin Degib1a07b82015-07-27 13:33:43 -06002647 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002648 if not os.path.isabs(ref_dir):
2649 # The alternate directory is relative to the object database.
2650 ref_dir = os.path.relpath(ref_dir,
2651 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002652 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2653 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002654
David Pursehousee8ace262020-02-13 12:41:15 +09002655 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002656
2657 m = self.manifest.manifestProject.config
2658 for key in ['user.name', 'user.email']:
2659 if m.Has(key, include_defaults=False):
2660 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002661 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002662 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002663 if self.manifest.IsMirror:
2664 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002665 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002666 self.config.SetString('core.bare', None)
2667 except Exception:
2668 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002669 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002670 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002671 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002672 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673
David Pursehousee8ace262020-02-13 12:41:15 +09002674 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002675 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002676 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002677
David Pursehousee8ace262020-02-13 12:41:15 +09002678 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002679 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002680 if not os.path.exists(hooks):
2681 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002682 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002683 name = os.path.basename(stock_hook)
2684
Victor Boivie65e0f352011-04-18 11:23:29 +02002685 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002686 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002687 # Don't install a Gerrit Code Review hook if this
2688 # project does not appear to use it for reviews.
2689 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002690 # Since the manifest project is one of those, but also
2691 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002692 continue
2693
2694 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002695 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002696 continue
2697 if os.path.exists(dst):
2698 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002699 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002700 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002701 if not quiet:
2702 _warn("%s: Not replacing locally modified %s hook",
2703 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002704 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002705 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002706 platform_utils.symlink(
2707 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002708 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002709 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002710 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002711 else:
2712 raise
2713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002714 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002715 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002716 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002717 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002718 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002719 remote.review = self.remote.review
2720 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002721
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002722 if self.worktree:
2723 remote.ResetFetch(mirror=False)
2724 else:
2725 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002726 remote.Save()
2727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002728 def _InitMRef(self):
2729 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002730 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002732 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002733 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002734
2735 def _InitAnyMRef(self, ref):
2736 cur = self.bare_ref.symref(ref)
2737
2738 if self.revisionId:
2739 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2740 msg = 'manifest set to %s' % self.revisionId
2741 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002742 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002743 else:
2744 remote = self.GetRemote(self.remote.name)
2745 dst = remote.ToLocal(self.revisionExpr)
2746 if cur != dst:
2747 msg = 'manifest set to %s' % self.revisionExpr
2748 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002749
Kevin Degi384b3c52014-10-16 16:02:58 -06002750 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002751 symlink_files = self.shareable_files[:]
2752 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002753 if share_refs:
2754 symlink_files += self.working_tree_files
2755 symlink_dirs += self.working_tree_dirs
2756 to_symlink = symlink_files + symlink_dirs
2757 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002758 # Try to self-heal a bit in simple cases.
2759 dst_path = os.path.join(destdir, name)
2760 src_path = os.path.join(srcdir, name)
2761
2762 if name in self.working_tree_dirs:
2763 # If the dir is missing under .repo/projects/, create it.
2764 if not os.path.exists(src_path):
2765 os.makedirs(src_path)
2766
2767 elif name in self.working_tree_files:
2768 # If it's a file under the checkout .git/ and the .repo/projects/ has
2769 # nothing, move the file under the .repo/projects/ tree.
2770 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2771 platform_utils.rename(dst_path, src_path)
2772
2773 # If the path exists under the .repo/projects/ and there's no symlink
2774 # under the checkout .git/, recreate the symlink.
2775 if name in self.working_tree_dirs or name in self.working_tree_files:
2776 if os.path.exists(src_path) and not os.path.exists(dst_path):
2777 platform_utils.symlink(
2778 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2779
2780 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002781 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002782 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002783 # Fail if the links are pointing to the wrong place
2784 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002785 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002786 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002787 'work tree. If you\'re comfortable with the '
2788 'possibility of losing the work tree\'s git metadata,'
2789 ' use `repo sync --force-sync {0}` to '
2790 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002791
David James8d201162013-10-11 17:03:19 -07002792 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2793 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2794
2795 Args:
2796 gitdir: The bare git repository. Must already be initialized.
2797 dotgit: The repository you would like to initialize.
2798 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2799 Only one work tree can store refs under a given |gitdir|.
2800 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2801 This saves you the effort of initializing |dotgit| yourself.
2802 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002803 symlink_files = self.shareable_files[:]
2804 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002805 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002806 symlink_files += self.working_tree_files
2807 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002808 to_symlink = symlink_files + symlink_dirs
2809
2810 to_copy = []
2811 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002812 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002813
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002814 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002815 for name in set(to_copy).union(to_symlink):
2816 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002817 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002818 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002819
Kevin Degi384b3c52014-10-16 16:02:58 -06002820 if os.path.lexists(dst):
2821 continue
David James8d201162013-10-11 17:03:19 -07002822
2823 # If the source dir doesn't exist, create an empty dir.
2824 if name in symlink_dirs and not os.path.lexists(src):
2825 os.makedirs(src)
2826
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002827 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002828 platform_utils.symlink(
2829 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002830 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002831 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002832 shutil.copytree(src, dst)
2833 elif os.path.isfile(src):
2834 shutil.copy(src, dst)
2835
Conley Owens80b87fe2014-05-09 17:13:44 -07002836 # If the source file doesn't exist, ensure the destination
2837 # file doesn't either.
2838 if name in symlink_files and not os.path.lexists(src):
2839 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002840 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002841 except OSError:
2842 pass
2843
David James8d201162013-10-11 17:03:19 -07002844 except OSError as e:
2845 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002846 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002847 else:
2848 raise
2849
Martin Kellye4e94d22017-03-21 16:05:12 -07002850 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002851 realdotgit = os.path.join(self.worktree, '.git')
2852 tmpdotgit = realdotgit + '.tmp'
2853 init_dotgit = not os.path.exists(realdotgit)
2854 if init_dotgit:
2855 dotgit = tmpdotgit
2856 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2857 os.makedirs(tmpdotgit)
2858 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2859 copy_all=False)
2860 else:
2861 dotgit = realdotgit
2862
Kevin Degib1a07b82015-07-27 13:33:43 -06002863 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002864 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2865 except GitError as e:
2866 if force_sync and not init_dotgit:
2867 try:
2868 platform_utils.rmtree(dotgit)
2869 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002870 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002871 raise e
2872 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002873
Mike Frysingerf4545122019-11-11 04:34:16 -05002874 if init_dotgit:
2875 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002876
Mike Frysingerf4545122019-11-11 04:34:16 -05002877 # Now that the .git dir is fully set up, move it to its final home.
2878 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002879
Mike Frysingerf4545122019-11-11 04:34:16 -05002880 # Finish checking out the worktree.
2881 cmd = ['read-tree', '--reset', '-u']
2882 cmd.append('-v')
2883 cmd.append(HEAD)
2884 if GitCommand(self, cmd).Wait() != 0:
2885 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002886
Mike Frysingerf4545122019-11-11 04:34:16 -05002887 if submodules:
2888 self._SyncSubmodules(quiet=True)
2889 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
Renaud Paquay788e9622017-01-27 11:41:12 -08002891 def _get_symlink_error_message(self):
2892 if platform_utils.isWindows():
2893 return ('Unable to create symbolic link. Please re-run the command as '
2894 'Administrator, or see '
2895 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2896 'for other options.')
2897 return 'filesystem must support symlinks'
2898
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002899 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002900 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002901
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002902 def _revlist(self, *args, **kw):
2903 a = []
2904 a.extend(args)
2905 a.append('--')
2906 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002907
2908 @property
2909 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002910 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002911
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002912 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002913 """Get logs between two revisions of this project."""
2914 comp = '..'
2915 if rev1:
2916 revs = [rev1]
2917 if rev2:
2918 revs.extend([comp, rev2])
2919 cmd = ['log', ''.join(revs)]
2920 out = DiffColoring(self.config)
2921 if out.is_on and color:
2922 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002923 if pretty_format is not None:
2924 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002925 if oneline:
2926 cmd.append('--oneline')
2927
2928 try:
2929 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2930 if log.Wait() == 0:
2931 return log.stdout
2932 except GitError:
2933 # worktree may not exist if groups changed for example. In that case,
2934 # try in gitdir instead.
2935 if not os.path.exists(self.worktree):
2936 return self.bare_git.log(*cmd[1:])
2937 else:
2938 raise
2939 return None
2940
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002941 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2942 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002943 """Get the list of logs from this revision to given revisionId"""
2944 logs = {}
2945 selfId = self.GetRevisionId(self._allrefs)
2946 toId = toProject.GetRevisionId(toProject._allrefs)
2947
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002948 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2949 pretty_format=pretty_format)
2950 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2951 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002952 return logs
2953
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002955
David James8d201162013-10-11 17:03:19 -07002956 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002957 self._project = project
2958 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002959 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002961 def LsOthers(self):
2962 p = GitCommand(self._project,
2963 ['ls-files',
2964 '-z',
2965 '--others',
2966 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002967 bare=False,
David James8d201162013-10-11 17:03:19 -07002968 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002969 capture_stdout=True,
2970 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971 if p.Wait() == 0:
2972 out = p.stdout
2973 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002974 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002975 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002976 return []
2977
2978 def DiffZ(self, name, *args):
2979 cmd = [name]
2980 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002981 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002982 cmd.extend(args)
2983 p = GitCommand(self._project,
2984 cmd,
David James8d201162013-10-11 17:03:19 -07002985 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002986 bare=False,
2987 capture_stdout=True,
2988 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002989 try:
2990 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002991 if not hasattr(out, 'encode'):
2992 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002993 r = {}
2994 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002995 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002996 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002997 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002998 info = next(out)
2999 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003000 except StopIteration:
3001 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003002
3003 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003004
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005 def __init__(self, path, omode, nmode, oid, nid, state):
3006 self.path = path
3007 self.src_path = None
3008 self.old_mode = omode
3009 self.new_mode = nmode
3010 self.old_id = oid
3011 self.new_id = nid
3012
3013 if len(state) == 1:
3014 self.status = state
3015 self.level = None
3016 else:
3017 self.status = state[:1]
3018 self.level = state[1:]
3019 while self.level.startswith('0'):
3020 self.level = self.level[1:]
3021
3022 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003023 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003024 if info.status in ('R', 'C'):
3025 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003026 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003027 r[info.path] = info
3028 return r
3029 finally:
3030 p.Wait()
3031
3032 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003033 if self._bare:
3034 path = os.path.join(self._project.gitdir, HEAD)
3035 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003036 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003037 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003038 with open(path) as fd:
3039 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003040 except IOError as e:
3041 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003042 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303043 line = line.decode()
3044 except AttributeError:
3045 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003046 if line.startswith('ref: '):
3047 return line[5:-1]
3048 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003049
3050 def SetHead(self, ref, message=None):
3051 cmdv = []
3052 if message is not None:
3053 cmdv.extend(['-m', message])
3054 cmdv.append(HEAD)
3055 cmdv.append(ref)
3056 self.symbolic_ref(*cmdv)
3057
3058 def DetachHead(self, new, message=None):
3059 cmdv = ['--no-deref']
3060 if message is not None:
3061 cmdv.extend(['-m', message])
3062 cmdv.append(HEAD)
3063 cmdv.append(new)
3064 self.update_ref(*cmdv)
3065
3066 def UpdateRef(self, name, new, old=None,
3067 message=None,
3068 detach=False):
3069 cmdv = []
3070 if message is not None:
3071 cmdv.extend(['-m', message])
3072 if detach:
3073 cmdv.append('--no-deref')
3074 cmdv.append(name)
3075 cmdv.append(new)
3076 if old is not None:
3077 cmdv.append(old)
3078 self.update_ref(*cmdv)
3079
3080 def DeleteRef(self, name, old=None):
3081 if not old:
3082 old = self.rev_parse(name)
3083 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003084 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003085
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003086 def rev_list(self, *args, **kw):
3087 if 'format' in kw:
3088 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3089 else:
3090 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003091 cmdv.extend(args)
3092 p = GitCommand(self._project,
3093 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003094 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003095 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003096 capture_stdout=True,
3097 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003098 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003099 raise GitError('%s rev-list %s: %s' %
3100 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003101 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003102
3103 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003104 """Allow arbitrary git commands using pythonic syntax.
3105
3106 This allows you to do things like:
3107 git_obj.rev_parse('HEAD')
3108
3109 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3110 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003111 Any other positional arguments will be passed to the git command, and the
3112 following keyword arguments are supported:
3113 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003114
3115 Args:
3116 name: The name of the git command to call. Any '_' characters will
3117 be replaced with '-'.
3118
3119 Returns:
3120 A callable object that will try to call git with the named command.
3121 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003122 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003123
Dave Borowitz091f8932012-10-23 17:01:04 -07003124 def runner(*args, **kwargs):
3125 cmdv = []
3126 config = kwargs.pop('config', None)
3127 for k in kwargs:
3128 raise TypeError('%s() got an unexpected keyword argument %r'
3129 % (name, k))
3130 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303131 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003132 cmdv.append('-c')
3133 cmdv.append('%s=%s' % (k, v))
3134 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003135 cmdv.extend(args)
3136 p = GitCommand(self._project,
3137 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003138 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003139 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003140 capture_stdout=True,
3141 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003143 raise GitError('%s %s: %s' %
3144 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003145 r = p.stdout
3146 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3147 return r[:-1]
3148 return r
3149 return runner
3150
3151
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003152class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003153
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003154 def __str__(self):
3155 return 'prior sync failed; rebase still in progress'
3156
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003159
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003160 def __str__(self):
3161 return 'contains uncommitted changes'
3162
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003163
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003164class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003165
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003166 def __init__(self, project, text):
3167 self.project = project
3168 self.text = text
3169
3170 def Print(self, syncbuf):
3171 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3172 syncbuf.out.nl()
3173
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177 def __init__(self, project, why):
3178 self.project = project
3179 self.why = why
3180
3181 def Print(self, syncbuf):
3182 syncbuf.out.fail('error: %s/: %s',
3183 self.project.relpath,
3184 str(self.why))
3185 syncbuf.out.nl()
3186
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003188class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003189
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003190 def __init__(self, project, action):
3191 self.project = project
3192 self.action = action
3193
3194 def Run(self, syncbuf):
3195 out = syncbuf.out
3196 out.project('project %s/', self.project.relpath)
3197 out.nl()
3198 try:
3199 self.action()
3200 out.nl()
3201 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003202 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003203 out.nl()
3204 return False
3205
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003206
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003207class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003208
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003209 def __init__(self, config):
3210 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003211 self.project = self.printer('header', attr='bold')
3212 self.info = self.printer('info')
3213 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003214
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003215
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003216class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003217
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218 def __init__(self, config, detach_head=False):
3219 self._messages = []
3220 self._failures = []
3221 self._later_queue1 = []
3222 self._later_queue2 = []
3223
3224 self.out = _SyncColoring(config)
3225 self.out.redirect(sys.stderr)
3226
3227 self.detach_head = detach_head
3228 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003229 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230
3231 def info(self, project, fmt, *args):
3232 self._messages.append(_InfoMessage(project, fmt % args))
3233
3234 def fail(self, project, err=None):
3235 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003236 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003237
3238 def later1(self, project, what):
3239 self._later_queue1.append(_Later(project, what))
3240
3241 def later2(self, project, what):
3242 self._later_queue2.append(_Later(project, what))
3243
3244 def Finish(self):
3245 self._PrintMessages()
3246 self._RunLater()
3247 self._PrintMessages()
3248 return self.clean
3249
David Rileye0684ad2017-04-05 00:02:59 -07003250 def Recently(self):
3251 recent_clean = self.recent_clean
3252 self.recent_clean = True
3253 return recent_clean
3254
3255 def _MarkUnclean(self):
3256 self.clean = False
3257 self.recent_clean = False
3258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003259 def _RunLater(self):
3260 for q in ['_later_queue1', '_later_queue2']:
3261 if not self._RunQueue(q):
3262 return
3263
3264 def _RunQueue(self, queue):
3265 for m in getattr(self, queue):
3266 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003267 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003268 return False
3269 setattr(self, queue, [])
3270 return True
3271
3272 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003273 if self._messages or self._failures:
3274 if os.isatty(2):
3275 self.out.write(progress.CSI_ERASE_LINE)
3276 self.out.write('\r')
3277
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003278 for m in self._messages:
3279 m.Print(self)
3280 for m in self._failures:
3281 m.Print(self)
3282
3283 self._messages = []
3284 self._failures = []
3285
3286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003287class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003289 """A special project housed under .repo.
3290 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003292 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003293 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003294 manifest=manifest,
3295 name=name,
3296 gitdir=gitdir,
3297 objdir=gitdir,
3298 worktree=worktree,
3299 remote=RemoteSpec('origin'),
3300 relpath='.repo/%s' % name,
3301 revisionExpr='refs/heads/master',
3302 revisionId=None,
3303 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003304
3305 def PreSync(self):
3306 if self.Exists:
3307 cb = self.CurrentBranch
3308 if cb:
3309 base = self.GetBranch(cb).merge
3310 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003311 self.revisionExpr = base
3312 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003313
Martin Kelly224a31a2017-07-10 14:46:25 -07003314 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003315 """ Prepare MetaProject for manifest branch switch
3316 """
3317
3318 # detach and delete manifest branch, allowing a new
3319 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003320 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003321 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003322 syncbuf.Finish()
3323
3324 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003325 ['update-ref', '-d', 'refs/heads/default'],
3326 capture_stdout=True,
3327 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003328
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003329 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003330 def LastFetch(self):
3331 try:
3332 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3333 return os.path.getmtime(fh)
3334 except OSError:
3335 return 0
3336
3337 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003338 def HasChanges(self):
3339 """Has the remote received new commits not yet checked out?
3340 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003341 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003342 return False
3343
David Pursehouse8a68ff92012-09-24 12:15:13 +09003344 all_refs = self.bare_ref.all
3345 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003346 head = self.work_git.GetHead()
3347 if head.startswith(R_HEADS):
3348 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003349 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003350 except KeyError:
3351 head = None
3352
3353 if revid == head:
3354 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003355 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003356 return True
3357 return False