blob: a5d35bf3325b028a00e54f084e953e4054d9b56d [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000204 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000216 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
242 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
256 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Mike Frysingere6a202f2019-08-02 15:57:57 -0400269def _SafeExpandPath(base, subpath, skipfinal=False):
270 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700271
Mike Frysingere6a202f2019-08-02 15:57:57 -0400272 We make sure no intermediate symlinks are traversed, and that the final path
273 is not a special file (e.g. not a socket or fifo).
274
275 NB: We rely on a number of paths already being filtered out while parsing the
276 manifest. See the validation logic in manifest_xml.py for more details.
277 """
278 components = subpath.split(os.path.sep)
279 if skipfinal:
280 # Whether the caller handles the final component itself.
281 finalpart = components.pop()
282
283 path = base
284 for part in components:
285 if part in {'.', '..'}:
286 raise ManifestInvalidPathError(
287 '%s: "%s" not allowed in paths' % (subpath, part))
288
289 path = os.path.join(path, part)
290 if platform_utils.islink(path):
291 raise ManifestInvalidPathError(
292 '%s: traversing symlinks not allow' % (path,))
293
294 if os.path.exists(path):
295 if not os.path.isfile(path) and not platform_utils.isdir(path):
296 raise ManifestInvalidPathError(
297 '%s: only regular files & directories allowed' % (path,))
298
299 if skipfinal:
300 path = os.path.join(path, finalpart)
301
302 return path
303
304
305class _CopyFile(object):
306 """Container for <copyfile> manifest element."""
307
308 def __init__(self, git_worktree, src, topdir, dest):
309 """Register a <copyfile> request.
310
311 Args:
312 git_worktree: Absolute path to the git project checkout.
313 src: Relative path under |git_worktree| of file to read.
314 topdir: Absolute path to the top of the repo client checkout.
315 dest: Relative path under |topdir| of file to write.
316 """
317 self.git_worktree = git_worktree
318 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700319 self.src = src
320 self.dest = dest
321
322 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400323 src = _SafeExpandPath(self.git_worktree, self.src)
324 dest = _SafeExpandPath(self.topdir, self.dest)
325
326 if platform_utils.isdir(src):
327 raise ManifestInvalidPathError(
328 '%s: copying from directory not supported' % (self.src,))
329 if platform_utils.isdir(dest):
330 raise ManifestInvalidPathError(
331 '%s: copying to directory not allowed' % (self.dest,))
332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333 # copy file if it does not exist or is out of date
334 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
335 try:
336 # remove existing file first, since it might be read-only
337 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800338 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400339 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700341 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200342 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 shutil.copy(src, dest)
344 # make the file read-only
345 mode = os.stat(dest)[stat.ST_MODE]
346 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
347 os.chmod(dest, mode)
348 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700349 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700351
Anthony King7bdac712014-07-16 12:56:40 +0100352class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700354
Mike Frysingere6a202f2019-08-02 15:57:57 -0400355 def __init__(self, git_worktree, src, topdir, dest):
356 """Register a <linkfile> request.
357
358 Args:
359 git_worktree: Absolute path to the git project checkout.
360 src: Target of symlink relative to path under |git_worktree|.
361 topdir: Absolute path to the top of the repo client checkout.
362 dest: Relative path under |topdir| of symlink to create.
363 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700364 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400365 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366 self.src = src
367 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368
Wink Saville4c426ef2015-06-03 08:05:17 -0700369 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700371 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500372 try:
373 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800374 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800375 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500376 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700377 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700378 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700380 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700382 _error('Cannot link file %s to %s', relSrc, absDest)
383
384 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400385 """Link the self.src & self.dest paths.
386
387 Handles wild cards on the src linking all of the files in the source in to
388 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700389 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500390 # Some people use src="." to create stable links to projects. Lets allow
391 # that but reject all other uses of "." to keep things simple.
392 if self.src == '.':
393 src = self.git_worktree
394 else:
395 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400396
397 if os.path.exists(src):
398 # Entity exists so just a simple one to one link operation.
399 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
400 # dest & src are absolute paths at this point. Make sure the target of
401 # the symlink is relative in the context of the repo client checkout.
402 relpath = os.path.relpath(src, os.path.dirname(dest))
403 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700406 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400407 if os.path.exists(dest) and not platform_utils.isdir(dest):
408 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 # Create a releative path from source dir to destination dir
412 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400413 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700414
415 # Get the source file name
416 srcFile = os.path.basename(absSrcFile)
417
418 # Now form the final full paths to srcFile. They will be
419 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700421 relSrc = os.path.join(relSrcDir, srcFile)
422 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500423
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700426
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700427 def __init__(self,
428 name,
Anthony King7bdac712014-07-16 12:56:40 +0100429 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700430 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100431 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700432 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700433 orig_name=None,
434 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700435 self.name = name
436 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700437 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700438 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100439 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700440 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700441 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700443
Doug Anderson37282b42011-03-04 11:54:18 -0800444class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700445
Doug Anderson37282b42011-03-04 11:54:18 -0800446 """A RepoHook contains information about a script to run as a hook.
447
448 Hooks are used to run a python script before running an upload (for instance,
449 to run presubmit checks). Eventually, we may have hooks for other actions.
450
451 This shouldn't be confused with files in the 'repo/hooks' directory. Those
452 files are copied into each '.git/hooks' folder for each project. Repo-level
453 hooks are associated instead with repo actions.
454
455 Hooks are always python. When a hook is run, we will load the hook into the
456 interpreter and execute its main() function.
457 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700458
Doug Anderson37282b42011-03-04 11:54:18 -0800459 def __init__(self,
460 hook_type,
461 hooks_project,
462 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400463 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800464 abort_if_user_denies=False):
465 """RepoHook constructor.
466
467 Params:
468 hook_type: A string representing the type of hook. This is also used
469 to figure out the name of the file containing the hook. For
470 example: 'pre-upload'.
471 hooks_project: The project containing the repo hooks. If you have a
472 manifest, this is manifest.repo_hooks_project. OK if this is None,
473 which will make the hook a no-op.
474 topdir: Repo's top directory (the one containing the .repo directory).
475 Scripts will run with CWD as this directory. If you have a manifest,
476 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800478 abort_if_user_denies: If True, we'll throw a HookError() if the user
479 doesn't allow us to run the hook.
480 """
481 self._hook_type = hook_type
482 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800484 self._topdir = topdir
485 self._abort_if_user_denies = abort_if_user_denies
486
487 # Store the full path to the script for convenience.
488 if self._hooks_project:
489 self._script_fullpath = os.path.join(self._hooks_project.worktree,
490 self._hook_type + '.py')
491 else:
492 self._script_fullpath = None
493
494 def _GetHash(self):
495 """Return a hash of the contents of the hooks directory.
496
497 We'll just use git to do this. This hash has the property that if anything
498 changes in the directory we will return a different has.
499
500 SECURITY CONSIDERATION:
501 This hash only represents the contents of files in the hook directory, not
502 any other files imported or called by hooks. Changes to imported files
503 can change the script behavior without affecting the hash.
504
505 Returns:
506 A string representing the hash. This will always be ASCII so that it can
507 be printed to the user easily.
508 """
509 assert self._hooks_project, "Must have hooks to calculate their hash."
510
511 # We will use the work_git object rather than just calling GetRevisionId().
512 # That gives us a hash of the latest checked in version of the files that
513 # the user will actually be executing. Specifically, GetRevisionId()
514 # doesn't appear to change even if a user checks out a different version
515 # of the hooks repo (via git checkout) nor if a user commits their own revs.
516 #
517 # NOTE: Local (non-committed) changes will not be factored into this hash.
518 # I think this is OK, since we're really only worried about warning the user
519 # about upstream changes.
520 return self._hooks_project.work_git.rev_parse('HEAD')
521
522 def _GetMustVerb(self):
523 """Return 'must' if the hook is required; 'should' if not."""
524 if self._abort_if_user_denies:
525 return 'must'
526 else:
527 return 'should'
528
529 def _CheckForHookApproval(self):
530 """Check to see whether this hook has been approved.
531
Mike Frysinger40252c22016-08-15 21:23:44 -0400532 We'll accept approval of manifest URLs if they're using secure transports.
533 This way the user can say they trust the manifest hoster. For insecure
534 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800535
536 Note that we ask permission for each individual hook even though we use
537 the hash of all hooks when detecting changes. We'd like the user to be
538 able to approve / deny each hook individually. We only use the hash of all
539 hooks because there is no other easy way to detect changes to local imports.
540
541 Returns:
542 True if this hook is approved to run; False otherwise.
543
544 Raises:
545 HookError: Raised if the user doesn't approve and abort_if_user_denies
546 was passed to the consturctor.
547 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400548 if self._ManifestUrlHasSecureScheme():
549 return self._CheckForHookApprovalManifest()
550 else:
551 return self._CheckForHookApprovalHash()
552
553 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
554 changed_prompt):
555 """Check for approval for a particular attribute and hook.
556
557 Args:
558 subkey: The git config key under [repo.hooks.<hook_type>] to store the
559 last approved string.
560 new_val: The new value to compare against the last approved one.
561 main_prompt: Message to display to the user to ask for approval.
562 changed_prompt: Message explaining why we're re-asking for approval.
563
564 Returns:
565 True if this hook is approved to run; False otherwise.
566
567 Raises:
568 HookError: Raised if the user doesn't approve and abort_if_user_denies
569 was passed to the consturctor.
570 """
Doug Anderson37282b42011-03-04 11:54:18 -0800571 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400572 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800573
Mike Frysinger40252c22016-08-15 21:23:44 -0400574 # Get the last value that the user approved for this hook; may be None.
575 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800576
Mike Frysinger40252c22016-08-15 21:23:44 -0400577 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800578 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400579 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800580 # Approval matched. We're done.
581 return True
582 else:
583 # Give the user a reason why we're prompting, since they last told
584 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400585 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800586 else:
587 prompt = ''
588
589 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
590 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400591 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530592 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900593 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800594
595 # User is doing a one-time approval.
596 if response in ('y', 'yes'):
597 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400598 elif response == 'always':
599 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800600 return True
601
602 # For anything else, we'll assume no approval.
603 if self._abort_if_user_denies:
604 raise HookError('You must allow the %s hook or use --no-verify.' %
605 self._hook_type)
606
607 return False
608
Mike Frysinger40252c22016-08-15 21:23:44 -0400609 def _ManifestUrlHasSecureScheme(self):
610 """Check if the URI for the manifest is a secure transport."""
611 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
612 parse_results = urllib.parse.urlparse(self._manifest_url)
613 return parse_results.scheme in secure_schemes
614
615 def _CheckForHookApprovalManifest(self):
616 """Check whether the user has approved this manifest host.
617
618 Returns:
619 True if this hook is approved to run; False otherwise.
620 """
621 return self._CheckForHookApprovalHelper(
622 'approvedmanifest',
623 self._manifest_url,
624 'Run hook scripts from %s' % (self._manifest_url,),
625 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
626
627 def _CheckForHookApprovalHash(self):
628 """Check whether the user has approved the hooks repo.
629
630 Returns:
631 True if this hook is approved to run; False otherwise.
632 """
633 prompt = ('Repo %s run the script:\n'
634 ' %s\n'
635 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700636 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400637 return self._CheckForHookApprovalHelper(
638 'approvedhash',
639 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700640 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400641 'Scripts have changed since %s was allowed.' % (self._hook_type,))
642
Mike Frysingerf7c51602019-06-18 17:23:39 -0400643 @staticmethod
644 def _ExtractInterpFromShebang(data):
645 """Extract the interpreter used in the shebang.
646
647 Try to locate the interpreter the script is using (ignoring `env`).
648
649 Args:
650 data: The file content of the script.
651
652 Returns:
653 The basename of the main script interpreter, or None if a shebang is not
654 used or could not be parsed out.
655 """
656 firstline = data.splitlines()[:1]
657 if not firstline:
658 return None
659
660 # The format here can be tricky.
661 shebang = firstline[0].strip()
662 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
663 if not m:
664 return None
665
666 # If the using `env`, find the target program.
667 interp = m.group(1)
668 if os.path.basename(interp) == 'env':
669 interp = m.group(2)
670
671 return interp
672
673 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
674 """Execute the hook script through |interp|.
675
676 Note: Support for this feature should be dropped ~Jun 2021.
677
678 Args:
679 interp: The Python program to run.
680 context: Basic Python context to execute the hook inside.
681 kwargs: Arbitrary arguments to pass to the hook script.
682
683 Raises:
684 HookError: When the hooks failed for any reason.
685 """
686 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
687 script = """
688import json, os, sys
689path = '''%(path)s'''
690kwargs = json.loads('''%(kwargs)s''')
691context = json.loads('''%(context)s''')
692sys.path.insert(0, os.path.dirname(path))
693data = open(path).read()
694exec(compile(data, path, 'exec'), context)
695context['main'](**kwargs)
696""" % {
697 'path': self._script_fullpath,
698 'kwargs': json.dumps(kwargs),
699 'context': json.dumps(context),
700 }
701
702 # We pass the script via stdin to avoid OS argv limits. It also makes
703 # unhandled exception tracebacks less verbose/confusing for users.
704 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
705 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
706 proc.communicate(input=script.encode('utf-8'))
707 if proc.returncode:
708 raise HookError('Failed to run %s hook.' % (self._hook_type,))
709
710 def _ExecuteHookViaImport(self, data, context, **kwargs):
711 """Execute the hook code in |data| directly.
712
713 Args:
714 data: The code of the hook to execute.
715 context: Basic Python context to execute the hook inside.
716 kwargs: Arbitrary arguments to pass to the hook script.
717
718 Raises:
719 HookError: When the hooks failed for any reason.
720 """
721 # Exec, storing global context in the context dict. We catch exceptions
722 # and convert to a HookError w/ just the failing traceback.
723 try:
724 exec(compile(data, self._script_fullpath, 'exec'), context)
725 except Exception:
726 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
727 (traceback.format_exc(), self._hook_type))
728
729 # Running the script should have defined a main() function.
730 if 'main' not in context:
731 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
732
733 # Call the main function in the hook. If the hook should cause the
734 # build to fail, it will raise an Exception. We'll catch that convert
735 # to a HookError w/ just the failing traceback.
736 try:
737 context['main'](**kwargs)
738 except Exception:
739 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
740 'above.' % (traceback.format_exc(), self._hook_type))
741
Doug Anderson37282b42011-03-04 11:54:18 -0800742 def _ExecuteHook(self, **kwargs):
743 """Actually execute the given hook.
744
745 This will run the hook's 'main' function in our python interpreter.
746
747 Args:
748 kwargs: Keyword arguments to pass to the hook. These are often specific
749 to the hook type. For instance, pre-upload hooks will contain
750 a project_list.
751 """
752 # Keep sys.path and CWD stashed away so that we can always restore them
753 # upon function exit.
754 orig_path = os.getcwd()
755 orig_syspath = sys.path
756
757 try:
758 # Always run hooks with CWD as topdir.
759 os.chdir(self._topdir)
760
761 # Put the hook dir as the first item of sys.path so hooks can do
762 # relative imports. We want to replace the repo dir as [0] so
763 # hooks can't import repo files.
764 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
765
Mike Frysingerf7c51602019-06-18 17:23:39 -0400766 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500767 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800768
Doug Anderson37282b42011-03-04 11:54:18 -0800769 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
770 # We don't actually want hooks to define their main with this argument--
771 # it's there to remind them that their hook should always take **kwargs.
772 # For instance, a pre-upload hook should be defined like:
773 # def main(project_list, **kwargs):
774 #
775 # This allows us to later expand the API without breaking old hooks.
776 kwargs = kwargs.copy()
777 kwargs['hook_should_take_kwargs'] = True
778
Mike Frysingerf7c51602019-06-18 17:23:39 -0400779 # See what version of python the hook has been written against.
780 data = open(self._script_fullpath).read()
781 interp = self._ExtractInterpFromShebang(data)
782 reexec = False
783 if interp:
784 prog = os.path.basename(interp)
785 if prog.startswith('python2') and sys.version_info.major != 2:
786 reexec = True
787 elif prog.startswith('python3') and sys.version_info.major == 2:
788 reexec = True
789
790 # Attempt to execute the hooks through the requested version of Python.
791 if reexec:
792 try:
793 self._ExecuteHookViaReexec(interp, context, **kwargs)
794 except OSError as e:
795 if e.errno == errno.ENOENT:
796 # We couldn't find the interpreter, so fallback to importing.
797 reexec = False
798 else:
799 raise
800
801 # Run the hook by importing directly.
802 if not reexec:
803 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800804 finally:
805 # Restore sys.path and CWD.
806 sys.path = orig_syspath
807 os.chdir(orig_path)
808
809 def Run(self, user_allows_all_hooks, **kwargs):
810 """Run the hook.
811
812 If the hook doesn't exist (because there is no hooks project or because
813 this particular hook is not enabled), this is a no-op.
814
815 Args:
816 user_allows_all_hooks: If True, we will never prompt about running the
817 hook--we'll just assume it's OK to run it.
818 kwargs: Keyword arguments to pass to the hook. These are often specific
819 to the hook type. For instance, pre-upload hooks will contain
820 a project_list.
821
822 Raises:
823 HookError: If there was a problem finding the hook or the user declined
824 to run a required hook (from _CheckForHookApproval).
825 """
826 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700827 if ((not self._hooks_project) or (self._hook_type not in
828 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800829 return
830
831 # Bail with a nice error if we can't find the hook.
832 if not os.path.isfile(self._script_fullpath):
833 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
834
835 # Make sure the user is OK with running the hook.
836 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
837 return
838
839 # Run the hook with the same version of python we're using.
840 self._ExecuteHook(**kwargs)
841
842
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600844 # These objects can be shared between several working trees.
845 shareable_files = ['description', 'info']
846 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
847 # These objects can only be used by a single working tree.
848 working_tree_files = ['config', 'packed-refs', 'shallow']
849 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700850
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 def __init__(self,
852 manifest,
853 name,
854 remote,
855 gitdir,
David James8d201162013-10-11 17:03:19 -0700856 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 worktree,
858 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700859 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800860 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100861 rebase=True,
862 groups=None,
863 sync_c=False,
864 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900865 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100866 clone_depth=None,
867 upstream=None,
868 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500869 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100870 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900871 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700872 optimized_fetch=False,
873 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800874 """Init a Project object.
875
876 Args:
877 manifest: The XmlManifest object.
878 name: The `name` attribute of manifest.xml's project element.
879 remote: RemoteSpec object specifying its remote's properties.
880 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700881 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800882 worktree: Absolute path of git working tree.
883 relpath: Relative path of git working tree to repo's top directory.
884 revisionExpr: The `revision` attribute of manifest.xml's project element.
885 revisionId: git commit id for checking out.
886 rebase: The `rebase` attribute of manifest.xml's project element.
887 groups: The `groups` attribute of manifest.xml's project element.
888 sync_c: The `sync-c` attribute of manifest.xml's project element.
889 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900890 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800891 upstream: The `upstream` attribute of manifest.xml's project element.
892 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500893 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800894 is_derived: False if the project was explicitly defined in the manifest;
895 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400896 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900897 optimized_fetch: If True, when a project is set to a sha1 revision, only
898 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700899 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800900 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 self.manifest = manifest
902 self.name = name
903 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800904 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700905 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800906 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700907 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800908 else:
909 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700911 self.revisionExpr = revisionExpr
912
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700913 if revisionId is None \
914 and revisionExpr \
915 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700916 self.revisionId = revisionExpr
917 else:
918 self.revisionId = revisionId
919
Mike Pontillod3153822012-02-28 11:53:24 -0800920 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700921 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700922 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800923 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900924 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900925 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700926 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800927 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500928 # NB: Do not use this setting in __init__ to change behavior so that the
929 # manifest.git checkout can inspect & change it after instantiating. See
930 # the XmlManifest init code for more info.
931 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800932 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900933 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800934 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800935
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500938 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500939 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700940 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
941 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800943 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700944 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800945 else:
946 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700947 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700948 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700949 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400950 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700951 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952
Doug Anderson37282b42011-03-04 11:54:18 -0800953 # This will be filled in if a project is later identified to be the
954 # project containing repo hooks.
955 self.enabled_repo_hooks = []
956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800958 def Derived(self):
959 return self.is_derived
960
961 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700963 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964
965 @property
966 def CurrentBranch(self):
967 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400968
969 The branch name omits the 'refs/heads/' prefix.
970 None is returned if the project is on a detached HEAD, or if the work_git is
971 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400973 try:
974 b = self.work_git.GetHead()
975 except NoManifestException:
976 # If the local checkout is in a bad state, don't barf. Let the callers
977 # process this like the head is unreadable.
978 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 if b.startswith(R_HEADS):
980 return b[len(R_HEADS):]
981 return None
982
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700983 def IsRebaseInProgress(self):
984 w = self.worktree
985 g = os.path.join(w, '.git')
986 return os.path.exists(os.path.join(g, 'rebase-apply')) \
987 or os.path.exists(os.path.join(g, 'rebase-merge')) \
988 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200989
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 def IsDirty(self, consider_untracked=True):
991 """Is the working directory modified in some way?
992 """
993 self.work_git.update_index('-q',
994 '--unmerged',
995 '--ignore-missing',
996 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900997 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 return True
999 if self.work_git.DiffZ('diff-files'):
1000 return True
1001 if consider_untracked and self.work_git.LsOthers():
1002 return True
1003 return False
1004
1005 _userident_name = None
1006 _userident_email = None
1007
1008 @property
1009 def UserName(self):
1010 """Obtain the user's personal name.
1011 """
1012 if self._userident_name is None:
1013 self._LoadUserIdentity()
1014 return self._userident_name
1015
1016 @property
1017 def UserEmail(self):
1018 """Obtain the user's email address. This is very likely
1019 to be their Gerrit login.
1020 """
1021 if self._userident_email is None:
1022 self._LoadUserIdentity()
1023 return self._userident_email
1024
1025 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001026 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1027 m = re.compile("^(.*) <([^>]*)> ").match(u)
1028 if m:
1029 self._userident_name = m.group(1)
1030 self._userident_email = m.group(2)
1031 else:
1032 self._userident_name = ''
1033 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
1035 def GetRemote(self, name):
1036 """Get the configuration for a single remote.
1037 """
1038 return self.config.GetRemote(name)
1039
1040 def GetBranch(self, name):
1041 """Get the configuration for a single branch.
1042 """
1043 return self.config.GetBranch(name)
1044
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001045 def GetBranches(self):
1046 """Get all existing local branches.
1047 """
1048 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001050 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001051
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301052 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001053 if name.startswith(R_HEADS):
1054 name = name[len(R_HEADS):]
1055 b = self.GetBranch(name)
1056 b.current = name == current
1057 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001058 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001059 heads[name] = b
1060
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301061 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001062 if name.startswith(R_PUB):
1063 name = name[len(R_PUB):]
1064 b = heads.get(name)
1065 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001066 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001067
1068 return heads
1069
Colin Cross5acde752012-03-28 20:15:45 -07001070 def MatchesGroups(self, manifest_groups):
1071 """Returns true if the manifest groups specified at init should cause
1072 this project to be synced.
1073 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001074 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001075
1076 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001077 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001078 manifest_groups: "-group1,group2"
1079 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001080
1081 The special manifest group "default" will match any project that
1082 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001083 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001084 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001085 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001086 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001087 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001088
Conley Owens971de8e2012-04-16 10:36:08 -07001089 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001090 for group in expanded_manifest_groups:
1091 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001092 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001093 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001094 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001095
Conley Owens971de8e2012-04-16 10:36:08 -07001096 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001098# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001099 def UncommitedFiles(self, get_all=True):
1100 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001102 Args:
1103 get_all: a boolean, if True - get information about all different
1104 uncommitted files. If False - return as soon as any kind of
1105 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001106 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001108 self.work_git.update_index('-q',
1109 '--unmerged',
1110 '--ignore-missing',
1111 '--refresh')
1112 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001113 details.append("rebase in progress")
1114 if not get_all:
1115 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001116
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001117 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1118 if changes:
1119 details.extend(changes)
1120 if not get_all:
1121 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001122
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001123 changes = self.work_git.DiffZ('diff-files').keys()
1124 if changes:
1125 details.extend(changes)
1126 if not get_all:
1127 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001128
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001129 changes = self.work_git.LsOthers()
1130 if changes:
1131 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001132
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001133 return details
1134
1135 def HasChanges(self):
1136 """Returns true if there are uncommitted changes.
1137 """
1138 if self.UncommitedFiles(get_all=False):
1139 return True
1140 else:
1141 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001142
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001143 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001145
1146 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001147 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001148 quiet: If True then only print the project name. Do not print
1149 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001151 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001152 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001153 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001154 print(file=output_redir)
1155 print('project %s/' % self.relpath, file=output_redir)
1156 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157 return
1158
1159 self.work_git.update_index('-q',
1160 '--unmerged',
1161 '--ignore-missing',
1162 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001163 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1165 df = self.work_git.DiffZ('diff-files')
1166 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001167 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001168 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169
1170 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001171 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001172 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001173 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001175 if quiet:
1176 out.nl()
1177 return 'DIRTY'
1178
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 branch = self.CurrentBranch
1180 if branch is None:
1181 out.nobranch('(*** NO BRANCH ***)')
1182 else:
1183 out.branch('branch %s', branch)
1184 out.nl()
1185
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001186 if rb:
1187 out.important('prior sync failed; rebase still in progress')
1188 out.nl()
1189
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 paths = list()
1191 paths.extend(di.keys())
1192 paths.extend(df.keys())
1193 paths.extend(do)
1194
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301195 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001196 try:
1197 i = di[p]
1198 except KeyError:
1199 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001201 try:
1202 f = df[p]
1203 except KeyError:
1204 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001205
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001206 if i:
1207 i_status = i.status.upper()
1208 else:
1209 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001211 if f:
1212 f_status = f.status.lower()
1213 else:
1214 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
1216 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001217 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001218 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219 else:
1220 line = ' %s%s\t%s' % (i_status, f_status, p)
1221
1222 if i and not f:
1223 out.added('%s', line)
1224 elif (i and f) or (not i and f):
1225 out.changed('%s', line)
1226 elif not i and not f:
1227 out.untracked('%s', line)
1228 else:
1229 out.write('%s', line)
1230 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001231
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001232 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233
pelyad67872d2012-03-28 14:49:58 +03001234 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 """Prints the status of the repository to stdout.
1236 """
1237 out = DiffColoring(self.config)
1238 cmd = ['diff']
1239 if out.is_on:
1240 cmd.append('--color')
1241 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001242 if absolute_paths:
1243 cmd.append('--src-prefix=a/%s/' % self.relpath)
1244 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001246 try:
1247 p = GitCommand(self,
1248 cmd,
1249 capture_stdout=True,
1250 capture_stderr=True)
1251 except GitError as e:
1252 out.nl()
1253 out.project('project %s/' % self.relpath)
1254 out.nl()
1255 out.fail('%s', str(e))
1256 out.nl()
1257 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258 has_diff = False
1259 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001260 if not hasattr(line, 'encode'):
1261 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 if not has_diff:
1263 out.nl()
1264 out.project('project %s/' % self.relpath)
1265 out.nl()
1266 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001267 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001268 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001269
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001270# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272 """Was the branch published (uploaded) for code review?
1273 If so, returns the SHA-1 hash of the last published
1274 state for the branch.
1275 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001277 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001278 try:
1279 return self.bare_git.rev_parse(key)
1280 except GitError:
1281 return None
1282 else:
1283 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001284 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001285 except KeyError:
1286 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287
David Pursehouse8a68ff92012-09-24 12:15:13 +09001288 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 """Prunes any stale published refs.
1290 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 if all_refs is None:
1292 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 heads = set()
1294 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301295 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 if name.startswith(R_HEADS):
1297 heads.add(name)
1298 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001299 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301301 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 n = name[len(R_PUB):]
1303 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001306 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 """List any branches which can be uploaded for review.
1308 """
1309 heads = {}
1310 pubed = {}
1311
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301312 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001314 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001316 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317
1318 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301319 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001320 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001322 if selected_branch and branch != selected_branch:
1323 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001325 rb = self.GetUploadableBranch(branch)
1326 if rb:
1327 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 return ready
1329
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001330 def GetUploadableBranch(self, branch_name):
1331 """Get a single uploadable branch, or None.
1332 """
1333 branch = self.GetBranch(branch_name)
1334 base = branch.LocalMerge
1335 if branch.LocalMerge:
1336 rb = ReviewableBranch(self, branch, base)
1337 if rb.commits:
1338 return rb
1339 return None
1340
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001341 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001342 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001343 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001344 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001345 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001346 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001347 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001348 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001349 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001350 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001351 validate_certs=True,
1352 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 """Uploads the named branch for code review.
1354 """
1355 if branch is None:
1356 branch = self.CurrentBranch
1357 if branch is None:
1358 raise GitError('not currently on a branch')
1359
1360 branch = self.GetBranch(branch)
1361 if not branch.LocalMerge:
1362 raise GitError('branch %s does not track a remote' % branch.name)
1363 if not branch.remote.review:
1364 raise GitError('remote %s has no review url' % branch.remote.name)
1365
Bryan Jacobsf609f912013-05-06 13:36:24 -04001366 if dest_branch is None:
1367 dest_branch = self.dest_branch
1368 if dest_branch is None:
1369 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 if not dest_branch.startswith(R_HEADS):
1371 dest_branch = R_HEADS + dest_branch
1372
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001373 if not branch.remote.projectname:
1374 branch.remote.projectname = self.name
1375 branch.remote.Save()
1376
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001377 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001378 if url is None:
1379 raise UploadError('review not configured')
1380 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001381 if dryrun:
1382 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001383
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001384 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001385 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001386
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001387 for push_option in (push_options or []):
1388 cmd.append('-o')
1389 cmd.append(push_option)
1390
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001391 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001392
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001393 if dest_branch.startswith(R_HEADS):
1394 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001395
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001396 upload_type = 'for'
1397 if draft:
1398 upload_type = 'drafts'
1399
1400 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1401 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001402 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001403 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001404 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001405 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001406
David Pursehousef25a3702018-11-14 19:01:22 -08001407 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001408 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001409 if notify:
1410 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001411 if private:
1412 opts += ['private']
1413 if wip:
1414 opts += ['wip']
1415 if opts:
1416 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001417 cmd.append(ref_spec)
1418
Anthony King7bdac712014-07-16 12:56:40 +01001419 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001420 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
1422 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1423 self.bare_git.UpdateRef(R_PUB + branch.name,
1424 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001425 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001427# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001428 def _ExtractArchive(self, tarpath, path=None):
1429 """Extract the given tar on its current location
1430
1431 Args:
1432 - tarpath: The path to the actual tar file
1433
1434 """
1435 try:
1436 with tarfile.open(tarpath, 'r') as tar:
1437 tar.extractall(path=path)
1438 return True
1439 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001440 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001441 return False
1442
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001443 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001444 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001445 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001446 is_new=None,
1447 current_branch_only=False,
1448 force_sync=False,
1449 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001450 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001451 archive=False,
1452 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001453 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001454 submodules=False,
1455 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456 """Perform only the network IO portion of the sync process.
1457 Local working directory/branch state is not affected.
1458 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001459 if archive and not isinstance(self, MetaProject):
1460 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001461 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001462 return False
1463
1464 name = self.relpath.replace('\\', '/')
1465 name = name.replace('/', '_')
1466 tarpath = '%s.tar' % name
1467 topdir = self.manifest.topdir
1468
1469 try:
1470 self._FetchArchive(tarpath, cwd=topdir)
1471 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001472 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001473 return False
1474
1475 # From now on, we only need absolute tarpath
1476 tarpath = os.path.join(topdir, tarpath)
1477
1478 if not self._ExtractArchive(tarpath, path=topdir):
1479 return False
1480 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001481 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001482 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001483 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001484 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001485 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001486 if is_new is None:
1487 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001488 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001489 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001490 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001491 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493
1494 if is_new:
1495 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1496 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001497 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001498 # This works for both absolute and relative alternate directories.
1499 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500 except IOError:
1501 alt_dir = None
1502 else:
1503 alt_dir = None
1504
Mike Frysingere50b6a72020-02-19 01:45:48 -05001505 if (clone_bundle
1506 and alt_dir is None
1507 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001508 is_new = False
1509
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001510 if not current_branch_only:
1511 if self.sync_c:
1512 current_branch_only = True
1513 elif not self.manifest._loaded:
1514 # Manifest cannot check defaults until it syncs.
1515 current_branch_only = False
1516 elif self.manifest.default.sync_c:
1517 current_branch_only = True
1518
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001519 if not self.sync_tags:
1520 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001521
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001522 if self.clone_depth:
1523 depth = self.clone_depth
1524 else:
1525 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1526
Mike Frysinger521d01b2020-02-17 01:51:49 -05001527 # See if we can skip the network fetch entirely.
1528 if not (optimized_fetch and
1529 (ID_RE.match(self.revisionExpr) and
1530 self._CheckForImmutableRevision())):
1531 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001532 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1533 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001534 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001535 submodules=submodules, force_sync=force_sync,
1536 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001537 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001538
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001539 mp = self.manifest.manifestProject
1540 dissociate = mp.config.GetBoolean('repo.dissociate')
1541 if dissociate:
1542 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1543 if os.path.exists(alternates_file):
1544 cmd = ['repack', '-a', '-d']
1545 if GitCommand(self, cmd, bare=True).Wait() != 0:
1546 return False
1547 platform_utils.remove(alternates_file)
1548
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001549 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001550 self._InitMRef()
1551 else:
1552 self._InitMirrorHead()
1553 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001554 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001555 except OSError:
1556 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001557 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001558
1559 def PostRepoUpgrade(self):
1560 self._InitHooks()
1561
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001562 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001563 if self.manifest.isGitcClient:
1564 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001565 for copyfile in self.copyfiles:
1566 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001567 for linkfile in self.linkfiles:
1568 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569
Julien Camperguedd654222014-01-09 16:21:37 +01001570 def GetCommitRevisionId(self):
1571 """Get revisionId of a commit.
1572
1573 Use this method instead of GetRevisionId to get the id of the commit rather
1574 than the id of the current git object (for example, a tag)
1575
1576 """
1577 if not self.revisionExpr.startswith(R_TAGS):
1578 return self.GetRevisionId(self._allrefs)
1579
1580 try:
1581 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1582 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001583 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1584 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001585
David Pursehouse8a68ff92012-09-24 12:15:13 +09001586 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001587 if self.revisionId:
1588 return self.revisionId
1589
1590 rem = self.GetRemote(self.remote.name)
1591 rev = rem.ToLocal(self.revisionExpr)
1592
David Pursehouse8a68ff92012-09-24 12:15:13 +09001593 if all_refs is not None and rev in all_refs:
1594 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001595
1596 try:
1597 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1598 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001599 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1600 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001601
Martin Kellye4e94d22017-03-21 16:05:12 -07001602 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 """Perform only the local IO portion of the sync process.
1604 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001606 if not os.path.exists(self.gitdir):
1607 syncbuf.fail(self,
1608 'Cannot checkout %s due to missing network sync; Run '
1609 '`repo sync -n %s` first.' %
1610 (self.name, self.name))
1611 return
1612
Martin Kellye4e94d22017-03-21 16:05:12 -07001613 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001614 all_refs = self.bare_ref.all
1615 self.CleanPublishedCache(all_refs)
1616 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001617
David Pursehouse1d947b32012-10-25 12:23:11 +09001618 def _doff():
1619 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001620 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001621
Martin Kellye4e94d22017-03-21 16:05:12 -07001622 def _dosubmodules():
1623 self._SyncSubmodules(quiet=True)
1624
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001625 head = self.work_git.GetHead()
1626 if head.startswith(R_HEADS):
1627 branch = head[len(R_HEADS):]
1628 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001629 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001630 except KeyError:
1631 head = None
1632 else:
1633 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001634
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001635 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001636 # Currently on a detached HEAD. The user is assumed to
1637 # not have any local modifications worth worrying about.
1638 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001639 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001640 syncbuf.fail(self, _PriorSyncFailedError())
1641 return
1642
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001643 if head == revid:
1644 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001645 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001646 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001647 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001648 # The copy/linkfile config may have changed.
1649 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001650 return
1651 else:
1652 lost = self._revlist(not_rev(revid), HEAD)
1653 if lost:
1654 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001655
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001657 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001658 if submodules:
1659 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001660 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001661 syncbuf.fail(self, e)
1662 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001663 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001664 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001666 if head == revid:
1667 # No changes; don't do anything further.
1668 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001669 # The copy/linkfile config may have changed.
1670 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001671 return
1672
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001673 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001674
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001675 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001677 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001679 syncbuf.info(self,
1680 "leaving %s; does not track upstream",
1681 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001683 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001684 if submodules:
1685 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001686 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001687 syncbuf.fail(self, e)
1688 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001689 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001690 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001692 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001693
1694 # See if we can perform a fast forward merge. This can happen if our
1695 # branch isn't in the exact same state as we last published.
1696 try:
1697 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1698 # Skip the published logic.
1699 pub = False
1700 except GitError:
1701 pub = self.WasPublished(branch.name, all_refs)
1702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001704 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 if not_merged:
1706 if upstream_gain:
1707 # The user has published this branch and some of those
1708 # commits are not yet merged upstream. We do not want
1709 # to rewrite the published commits so we punt.
1710 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001711 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001712 "branch %s is published (but not merged) and is now "
1713 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001714 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001715 elif pub == head:
1716 # All published commits are merged, and thus we are a
1717 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001718 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001719 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001720 if submodules:
1721 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001722 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001724 # Examine the local commits not in the remote. Find the
1725 # last one attributed to this user, if any.
1726 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001727 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001728 last_mine = None
1729 cnt_mine = 0
1730 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001731 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001732 if committer_email == self.UserEmail:
1733 last_mine = commit_id
1734 cnt_mine += 1
1735
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001736 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001737 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738
1739 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001740 syncbuf.fail(self, _DirtyError())
1741 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001743 # If the upstream switched on us, warn the user.
1744 #
1745 if branch.merge != self.revisionExpr:
1746 if branch.merge and self.revisionExpr:
1747 syncbuf.info(self,
1748 'manifest switched %s...%s',
1749 branch.merge,
1750 self.revisionExpr)
1751 elif branch.merge:
1752 syncbuf.info(self,
1753 'manifest no longer tracks %s',
1754 branch.merge)
1755
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001756 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001758 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001759 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001760 syncbuf.info(self,
1761 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001762 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001764 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001765 if not ID_RE.match(self.revisionExpr):
1766 # in case of manifest sync the revisionExpr might be a SHA1
1767 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001768 if not branch.merge.startswith('refs/'):
1769 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770 branch.Save()
1771
Mike Pontillod3153822012-02-28 11:53:24 -08001772 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001773 def _docopyandlink():
1774 self._CopyAndLinkFiles()
1775
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001776 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001777 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001778 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001779 if submodules:
1780 syncbuf.later2(self, _dosubmodules)
1781 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001782 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001784 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001785 if submodules:
1786 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001787 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001788 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001789 syncbuf.fail(self, e)
1790 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001792 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001793 if submodules:
1794 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001795
Mike Frysingere6a202f2019-08-02 15:57:57 -04001796 def AddCopyFile(self, src, dest, topdir):
1797 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798
Mike Frysingere6a202f2019-08-02 15:57:57 -04001799 No filesystem changes occur here. Actual copying happens later on.
1800
1801 Paths should have basic validation run on them before being queued.
1802 Further checking will be handled when the actual copy happens.
1803 """
1804 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1805
1806 def AddLinkFile(self, src, dest, topdir):
1807 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1808
1809 No filesystem changes occur here. Actual linking happens later on.
1810
1811 Paths should have basic validation run on them before being queued.
1812 Further checking will be handled when the actual link happens.
1813 """
1814 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001815
James W. Mills24c13082012-04-12 15:04:13 -05001816 def AddAnnotation(self, name, value, keep):
1817 self.annotations.append(_Annotation(name, value, keep))
1818
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001819 def DownloadPatchSet(self, change_id, patch_id):
1820 """Download a single patch set of a single change to FETCH_HEAD.
1821 """
1822 remote = self.GetRemote(self.remote.name)
1823
1824 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001825 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001826 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001827 if GitCommand(self, cmd, bare=True).Wait() != 0:
1828 return None
1829 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001830 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001831 change_id,
1832 patch_id,
1833 self.bare_git.rev_parse('FETCH_HEAD'))
1834
Mike Frysingerc0d18662020-02-19 19:19:18 -05001835 def DeleteWorktree(self, quiet=False, force=False):
1836 """Delete the source checkout and any other housekeeping tasks.
1837
1838 This currently leaves behind the internal .repo/ cache state. This helps
1839 when switching branches or manifest changes get reverted as we don't have
1840 to redownload all the git objects. But we should do some GC at some point.
1841
1842 Args:
1843 quiet: Whether to hide normal messages.
1844 force: Always delete tree even if dirty.
1845
1846 Returns:
1847 True if the worktree was completely cleaned out.
1848 """
1849 if self.IsDirty():
1850 if force:
1851 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1852 (self.relpath,), file=sys.stderr)
1853 else:
1854 print('error: %s: Cannot remove project: uncommitted changes are '
1855 'present.\n' % (self.relpath,), file=sys.stderr)
1856 return False
1857
1858 if not quiet:
1859 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1860
1861 # Unlock and delink from the main worktree. We don't use git's worktree
1862 # remove because it will recursively delete projects -- we handle that
1863 # ourselves below. https://crbug.com/git/48
1864 if self.use_git_worktrees:
1865 needle = platform_utils.realpath(self.gitdir)
1866 # Find the git worktree commondir under .repo/worktrees/.
1867 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1868 assert output.startswith('worktree '), output
1869 commondir = output[9:]
1870 # Walk each of the git worktrees to see where they point.
1871 configs = os.path.join(commondir, 'worktrees')
1872 for name in os.listdir(configs):
1873 gitdir = os.path.join(configs, name, 'gitdir')
1874 with open(gitdir) as fp:
1875 relpath = fp.read().strip()
1876 # Resolve the checkout path and see if it matches this project.
1877 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1878 if fullpath == needle:
1879 platform_utils.rmtree(os.path.join(configs, name))
1880
1881 # Delete the .git directory first, so we're less likely to have a partially
1882 # working git repository around. There shouldn't be any git projects here,
1883 # so rmtree works.
1884
1885 # Try to remove plain files first in case of git worktrees. If this fails
1886 # for any reason, we'll fall back to rmtree, and that'll display errors if
1887 # it can't remove things either.
1888 try:
1889 platform_utils.remove(self.gitdir)
1890 except OSError:
1891 pass
1892 try:
1893 platform_utils.rmtree(self.gitdir)
1894 except OSError as e:
1895 if e.errno != errno.ENOENT:
1896 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1897 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1898 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1899 return False
1900
1901 # Delete everything under the worktree, except for directories that contain
1902 # another git project.
1903 dirs_to_remove = []
1904 failed = False
1905 for root, dirs, files in platform_utils.walk(self.worktree):
1906 for f in files:
1907 path = os.path.join(root, f)
1908 try:
1909 platform_utils.remove(path)
1910 except OSError as e:
1911 if e.errno != errno.ENOENT:
1912 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1913 failed = True
1914 dirs[:] = [d for d in dirs
1915 if not os.path.lexists(os.path.join(root, d, '.git'))]
1916 dirs_to_remove += [os.path.join(root, d) for d in dirs
1917 if os.path.join(root, d) not in dirs_to_remove]
1918 for d in reversed(dirs_to_remove):
1919 if platform_utils.islink(d):
1920 try:
1921 platform_utils.remove(d)
1922 except OSError as e:
1923 if e.errno != errno.ENOENT:
1924 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1925 failed = True
1926 elif not platform_utils.listdir(d):
1927 try:
1928 platform_utils.rmdir(d)
1929 except OSError as e:
1930 if e.errno != errno.ENOENT:
1931 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1932 failed = True
1933 if failed:
1934 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1935 file=sys.stderr)
1936 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1937 return False
1938
1939 # Try deleting parent dirs if they are empty.
1940 path = self.worktree
1941 while path != self.manifest.topdir:
1942 try:
1943 platform_utils.rmdir(path)
1944 except OSError as e:
1945 if e.errno != errno.ENOENT:
1946 break
1947 path = os.path.dirname(path)
1948
1949 return True
1950
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001951# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001952 def GetHeadPath(self):
1953 """Return the full path to the HEAD ref."""
1954 dotgit = os.path.join(self.worktree, '.git')
1955 if os.path.isfile(dotgit):
1956 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1957 with open(dotgit) as fp:
1958 setting = fp.read()
1959 assert setting.startswith('gitdir:')
1960 gitdir = setting.split(':', 1)[1].strip()
1961 dotgit = os.path.join(self.worktree, gitdir)
1962 return os.path.join(dotgit, HEAD)
1963
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001964 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001965 """Create a new branch off the manifest's revision.
1966 """
Simran Basib9a1b732015-08-20 12:19:28 -07001967 if not branch_merge:
1968 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001969 head = self.work_git.GetHead()
1970 if head == (R_HEADS + name):
1971 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972
David Pursehouse8a68ff92012-09-24 12:15:13 +09001973 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001974 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001975 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001976 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001977 capture_stdout=True,
1978 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001979
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001980 branch = self.GetBranch(name)
1981 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001982 branch.merge = branch_merge
1983 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1984 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001985
1986 if revision is None:
1987 revid = self.GetRevisionId(all_refs)
1988 else:
1989 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001990
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001991 if head.startswith(R_HEADS):
1992 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001993 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001994 except KeyError:
1995 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001996 if revid and head and revid == head:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001997 if self.use_git_worktrees:
1998 self.work_git.update_ref(HEAD, revid)
1999 branch.Save()
2000 else:
2001 ref = os.path.join(self.gitdir, R_HEADS + name)
2002 try:
2003 os.makedirs(os.path.dirname(ref))
2004 except OSError:
2005 pass
2006 _lwrite(ref, '%s\n' % revid)
2007 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
2008 branch.Save()
2009 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002010
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002011 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002012 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002013 capture_stdout=True,
2014 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002015 branch.Save()
2016 return True
2017 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002018
Wink Saville02d79452009-04-10 13:01:24 -07002019 def CheckoutBranch(self, name):
2020 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002021
2022 Args:
2023 name: The name of the branch to checkout.
2024
2025 Returns:
2026 True if the checkout succeeded; False if it didn't; None if the branch
2027 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002028 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002029 rev = R_HEADS + name
2030 head = self.work_git.GetHead()
2031 if head == rev:
2032 # Already on the branch
2033 #
2034 return True
Wink Saville02d79452009-04-10 13:01:24 -07002035
David Pursehouse8a68ff92012-09-24 12:15:13 +09002036 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002037 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002038 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002039 except KeyError:
2040 # Branch does not exist in this project
2041 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002042 return None
Wink Saville02d79452009-04-10 13:01:24 -07002043
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002044 if head.startswith(R_HEADS):
2045 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002046 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002047 except KeyError:
2048 head = None
2049
2050 if head == revid:
2051 # Same revision; just update HEAD to point to the new
2052 # target branch, but otherwise take no other action.
2053 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05002054 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002055 return True
2056
2057 return GitCommand(self,
2058 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002059 capture_stdout=True,
2060 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002061
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002062 def AbandonBranch(self, name):
2063 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002064
2065 Args:
2066 name: The name of the branch to abandon.
2067
2068 Returns:
2069 True if the abandon succeeded; False if it didn't; None if the branch
2070 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002071 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002072 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002073 all_refs = self.bare_ref.all
2074 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002075 # Doesn't exist
2076 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002077
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002078 head = self.work_git.GetHead()
2079 if head == rev:
2080 # We can't destroy the branch while we are sitting
2081 # on it. Switch to a detached HEAD.
2082 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002083 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002084
David Pursehouse8a68ff92012-09-24 12:15:13 +09002085 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002086 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05002087 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002088 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002089 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002090
2091 return GitCommand(self,
2092 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002093 capture_stdout=True,
2094 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002095
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002096 def PruneHeads(self):
2097 """Prune any topic branches already merged into upstream.
2098 """
2099 cb = self.CurrentBranch
2100 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002101 left = self._allrefs
2102 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103 if name.startswith(R_HEADS):
2104 name = name[len(R_HEADS):]
2105 if cb is None or name != cb:
2106 kill.append(name)
2107
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002108 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109 if cb is not None \
2110 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002111 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002112 self.work_git.DetachHead(HEAD)
2113 kill.append(cb)
2114
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002116 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 try:
2119 self.bare_git.DetachHead(rev)
2120
2121 b = ['branch', '-d']
2122 b.extend(kill)
2123 b = GitCommand(self, b, bare=True,
2124 capture_stdout=True,
2125 capture_stderr=True)
2126 b.Wait()
2127 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002128 if ID_RE.match(old):
2129 self.bare_git.DetachHead(old)
2130 else:
2131 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002132 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002134 for branch in kill:
2135 if (R_HEADS + branch) not in left:
2136 self.CleanPublishedCache()
2137 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138
2139 if cb and cb not in kill:
2140 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002141 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002142
2143 kept = []
2144 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002145 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 branch = self.GetBranch(branch)
2147 base = branch.LocalMerge
2148 if not base:
2149 base = rev
2150 kept.append(ReviewableBranch(self, branch, base))
2151 return kept
2152
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002153# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002154 def GetRegisteredSubprojects(self):
2155 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002156
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002157 def rec(subprojects):
2158 if not subprojects:
2159 return
2160 result.extend(subprojects)
2161 for p in subprojects:
2162 rec(p.subprojects)
2163 rec(self.subprojects)
2164 return result
2165
2166 def _GetSubmodules(self):
2167 # Unfortunately we cannot call `git submodule status --recursive` here
2168 # because the working tree might not exist yet, and it cannot be used
2169 # without a working tree in its current implementation.
2170
2171 def get_submodules(gitdir, rev):
2172 # Parse .gitmodules for submodule sub_paths and sub_urls
2173 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2174 if not sub_paths:
2175 return []
2176 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2177 # revision of submodule repository
2178 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2179 submodules = []
2180 for sub_path, sub_url in zip(sub_paths, sub_urls):
2181 try:
2182 sub_rev = sub_revs[sub_path]
2183 except KeyError:
2184 # Ignore non-exist submodules
2185 continue
2186 submodules.append((sub_rev, sub_path, sub_url))
2187 return submodules
2188
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002189 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2190 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002191
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002192 def parse_gitmodules(gitdir, rev):
2193 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2194 try:
Anthony King7bdac712014-07-16 12:56:40 +01002195 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2196 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002197 except GitError:
2198 return [], []
2199 if p.Wait() != 0:
2200 return [], []
2201
2202 gitmodules_lines = []
2203 fd, temp_gitmodules_path = tempfile.mkstemp()
2204 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002205 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002206 os.close(fd)
2207 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002208 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2209 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002210 if p.Wait() != 0:
2211 return [], []
2212 gitmodules_lines = p.stdout.split('\n')
2213 except GitError:
2214 return [], []
2215 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002216 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002217
2218 names = set()
2219 paths = {}
2220 urls = {}
2221 for line in gitmodules_lines:
2222 if not line:
2223 continue
2224 m = re_path.match(line)
2225 if m:
2226 names.add(m.group(1))
2227 paths[m.group(1)] = m.group(2)
2228 continue
2229 m = re_url.match(line)
2230 if m:
2231 names.add(m.group(1))
2232 urls[m.group(1)] = m.group(2)
2233 continue
2234 names = sorted(names)
2235 return ([paths.get(name, '') for name in names],
2236 [urls.get(name, '') for name in names])
2237
2238 def git_ls_tree(gitdir, rev, paths):
2239 cmd = ['ls-tree', rev, '--']
2240 cmd.extend(paths)
2241 try:
Anthony King7bdac712014-07-16 12:56:40 +01002242 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2243 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002244 except GitError:
2245 return []
2246 if p.Wait() != 0:
2247 return []
2248 objects = {}
2249 for line in p.stdout.split('\n'):
2250 if not line.strip():
2251 continue
2252 object_rev, object_path = line.split()[2:4]
2253 objects[object_path] = object_rev
2254 return objects
2255
2256 try:
2257 rev = self.GetRevisionId()
2258 except GitError:
2259 return []
2260 return get_submodules(self.gitdir, rev)
2261
2262 def GetDerivedSubprojects(self):
2263 result = []
2264 if not self.Exists:
2265 # If git repo does not exist yet, querying its submodules will
2266 # mess up its states; so return here.
2267 return result
2268 for rev, path, url in self._GetSubmodules():
2269 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002270 relpath, worktree, gitdir, objdir = \
2271 self.manifest.GetSubprojectPaths(self, name, path)
2272 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002273 if project:
2274 result.extend(project.GetDerivedSubprojects())
2275 continue
David James8d201162013-10-11 17:03:19 -07002276
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002277 if url.startswith('..'):
2278 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002279 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002280 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002281 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002282 review=self.remote.review,
2283 revision=self.remote.revision)
2284 subproject = Project(manifest=self.manifest,
2285 name=name,
2286 remote=remote,
2287 gitdir=gitdir,
2288 objdir=objdir,
2289 worktree=worktree,
2290 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002291 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002292 revisionId=rev,
2293 rebase=self.rebase,
2294 groups=self.groups,
2295 sync_c=self.sync_c,
2296 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002297 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002298 parent=self,
2299 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002300 result.append(subproject)
2301 result.extend(subproject.GetDerivedSubprojects())
2302 return result
2303
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002304# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002305 def EnableRepositoryExtension(self, key, value='true', version=1):
2306 """Enable git repository extension |key| with |value|.
2307
2308 Args:
2309 key: The extension to enabled. Omit the "extensions." prefix.
2310 value: The value to use for the extension.
2311 version: The minimum git repository version needed.
2312 """
2313 # Make sure the git repo version is new enough already.
2314 found_version = self.config.GetInt('core.repositoryFormatVersion')
2315 if found_version is None:
2316 found_version = 0
2317 if found_version < version:
2318 self.config.SetString('core.repositoryFormatVersion', str(version))
2319
2320 # Enable the extension!
2321 self.config.SetString('extensions.%s' % (key,), value)
2322
Zac Livingstone4332262017-06-16 08:56:09 -06002323 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002324 try:
2325 # if revision (sha or tag) is not present then following function
2326 # throws an error.
2327 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2328 return True
2329 except GitError:
2330 # There is no such persistent revision. We have to fetch it.
2331 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332
Julien Campergue335f5ef2013-10-16 11:02:35 +02002333 def _FetchArchive(self, tarpath, cwd=None):
2334 cmd = ['archive', '-v', '-o', tarpath]
2335 cmd.append('--remote=%s' % self.remote.url)
2336 cmd.append('--prefix=%s/' % self.relpath)
2337 cmd.append(self.revisionExpr)
2338
2339 command = GitCommand(self, cmd, cwd=cwd,
2340 capture_stdout=True,
2341 capture_stderr=True)
2342
2343 if command.Wait() != 0:
2344 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2345
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002346 def _RemoteFetch(self, name=None,
2347 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002348 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002349 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002350 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002351 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002352 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002353 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002354 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002355 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002356 force_sync=False,
2357 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002358
2359 is_sha1 = False
2360 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002361 # The depth should not be used when fetching to a mirror because
2362 # it will result in a shallow repository that cannot be cloned or
2363 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002364 # The repo project should also never be synced with partial depth.
2365 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2366 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002367
Shawn Pearce69e04d82014-01-29 12:48:54 -08002368 if depth:
2369 current_branch_only = True
2370
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002371 if ID_RE.match(self.revisionExpr) is not None:
2372 is_sha1 = True
2373
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002374 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002375 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002376 # this is a tag and its sha1 value should never change
2377 tag_name = self.revisionExpr[len(R_TAGS):]
2378
2379 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002380 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002381 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002382 print('Skipped fetching project %s (already have persistent ref)'
2383 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002384 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002385 if is_sha1 and not depth:
2386 # When syncing a specific commit and --depth is not set:
2387 # * if upstream is explicitly specified and is not a sha1, fetch only
2388 # upstream as users expect only upstream to be fetch.
2389 # Note: The commit might not be in upstream in which case the sync
2390 # will fail.
2391 # * otherwise, fetch all branches to make sure we end up with the
2392 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002393 if self.upstream:
2394 current_branch_only = not ID_RE.match(self.upstream)
2395 else:
2396 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002397
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002398 if not name:
2399 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002400
2401 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002402 remote = self.GetRemote(name)
2403 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002404 ssh_proxy = True
2405
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002407 if alt_dir and 'objects' == os.path.basename(alt_dir):
2408 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002409 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2410 remote = self.GetRemote(name)
2411
David Pursehouse8a68ff92012-09-24 12:15:13 +09002412 all_refs = self.bare_ref.all
2413 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002414 tmp = set()
2415
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302416 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002417 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002418 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002419 all_refs[r] = ref_id
2420 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002421 continue
2422
David Pursehouse8a68ff92012-09-24 12:15:13 +09002423 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002424 continue
2425
David Pursehouse8a68ff92012-09-24 12:15:13 +09002426 r = 'refs/_alt/%s' % ref_id
2427 all_refs[r] = ref_id
2428 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002429 tmp.add(r)
2430
heping3d7bbc92017-04-12 19:51:47 +08002431 tmp_packed_lines = []
2432 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002433
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302434 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002435 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002436 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002437 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002438 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002439
heping3d7bbc92017-04-12 19:51:47 +08002440 tmp_packed = ''.join(tmp_packed_lines)
2441 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002442 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002443 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002444 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002445
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002446 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002447
Xin Li745be2e2019-06-03 11:24:30 -07002448 if clone_filter:
2449 git_require((2, 19, 0), fail=True, msg='partial clones')
2450 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002451 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002452
Conley Owensf97e8382015-01-21 11:12:46 -08002453 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002454 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002455 else:
2456 # If this repo has shallow objects, then we don't know which refs have
2457 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2458 # do this with projects that don't have shallow objects, since it is less
2459 # efficient.
2460 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2461 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002462
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002463 if quiet:
2464 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002465 if not self.worktree:
2466 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002467 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002468
Mike Frysingere57f1142019-03-18 21:27:54 -04002469 if force_sync:
2470 cmd.append('--force')
2471
David Pursehouse74cfd272015-10-14 10:50:15 +09002472 if prune:
2473 cmd.append('--prune')
2474
Martin Kellye4e94d22017-03-21 16:05:12 -07002475 if submodules:
2476 cmd.append('--recurse-submodules=on-demand')
2477
Kuang-che Wu6856f982019-11-25 12:37:55 +08002478 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002479 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002480 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002481 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002482 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002483 spec.append('tag')
2484 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002485
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302486 if self.manifest.IsMirror and not current_branch_only:
2487 branch = None
2488 else:
2489 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002490 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002491 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002492 # Shallow checkout of a specific commit, fetch from that commit and not
2493 # the heads only as the commit might be deeper in the history.
2494 spec.append(branch)
2495 else:
2496 if is_sha1:
2497 branch = self.upstream
2498 if branch is not None and branch.strip():
2499 if not branch.startswith('refs/'):
2500 branch = R_HEADS + branch
2501 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2502
2503 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2504 # whole repo.
2505 if self.manifest.IsMirror and not spec:
2506 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2507
2508 # If using depth then we should not get all the tags since they may
2509 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002510 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002511 cmd.append('--no-tags')
2512 else:
2513 cmd.append('--tags')
2514 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2515
Conley Owens80b87fe2014-05-09 17:13:44 -07002516 cmd.extend(spec)
2517
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002518 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002519 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002520 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2521 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002522 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002523 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002524 ok = True
2525 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002526 # If needed, run the 'git remote prune' the first time through the loop
2527 elif (not _i and
2528 "error:" in gitcmd.stderr and
2529 "git remote prune" in gitcmd.stderr):
2530 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002531 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002532 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002533 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002534 break
2535 continue
Brian Harring14a66742012-09-28 20:21:57 -07002536 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002537 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2538 # in sha1 mode, we just tried sync'ing from the upstream field; it
2539 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002540 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002541 elif ret < 0:
2542 # Git died with a signal, exit immediately
2543 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002544 if not verbose:
2545 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002546 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002547
2548 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002549 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002550 if old_packed != '':
2551 _lwrite(packed_refs, old_packed)
2552 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002553 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002554 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002555
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002556 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002557 # We just synced the upstream given branch; verify we
2558 # got what we wanted, else trigger a second run of all
2559 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002560 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002561 # Sync the current branch only with depth set to None.
2562 # We always pass depth=None down to avoid infinite recursion.
2563 return self._RemoteFetch(
2564 name=name, quiet=quiet, verbose=verbose,
2565 current_branch_only=current_branch_only and depth,
2566 initial=False, alt_dir=alt_dir,
2567 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002568
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002569 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002570
Mike Frysingere50b6a72020-02-19 01:45:48 -05002571 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002572 if initial and \
2573 (self.manifest.manifestProject.config.GetString('repo.depth') or
2574 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002575 return False
2576
2577 remote = self.GetRemote(self.remote.name)
2578 bundle_url = remote.url + '/clone.bundle'
2579 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002580 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2581 'persistent-http',
2582 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002583 return False
2584
2585 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2586 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2587
2588 exist_dst = os.path.exists(bundle_dst)
2589 exist_tmp = os.path.exists(bundle_tmp)
2590
2591 if not initial and not exist_dst and not exist_tmp:
2592 return False
2593
2594 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002595 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2596 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002597 if not exist_dst:
2598 return False
2599
2600 cmd = ['fetch']
2601 if quiet:
2602 cmd.append('--quiet')
2603 if not self.worktree:
2604 cmd.append('--update-head-ok')
2605 cmd.append(bundle_dst)
2606 for f in remote.fetch:
2607 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002608 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002609
2610 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002611 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002612 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002613 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002614 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002615 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616
Mike Frysingere50b6a72020-02-19 01:45:48 -05002617 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002618 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002619 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002620
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002621 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002622 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002623 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002624 if os.path.exists(tmpPath):
2625 size = os.stat(tmpPath).st_size
2626 if size >= 1024:
2627 cmd += ['--continue-at', '%d' % (size,)]
2628 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002629 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002630 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002631 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002632 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002633 if proxy:
2634 cmd += ['--proxy', proxy]
2635 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2636 cmd += ['--proxy', os.environ['http_proxy']]
2637 if srcUrl.startswith('persistent-https'):
2638 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2639 elif srcUrl.startswith('persistent-http'):
2640 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002641 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002642
Dave Borowitz137d0132015-01-02 11:12:54 -08002643 if IsTrace():
2644 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002645 if verbose:
2646 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2647 stdout = None if verbose else subprocess.PIPE
2648 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002649 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002650 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002651 except OSError:
2652 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002653
Mike Frysingere50b6a72020-02-19 01:45:48 -05002654 (output, _) = proc.communicate()
2655 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002656
Dave Borowitz137d0132015-01-02 11:12:54 -08002657 if curlret == 22:
2658 # From curl man page:
2659 # 22: HTTP page not retrieved. The requested url was not found or
2660 # returned another error with the HTTP error code being 400 or above.
2661 # This return code only appears if -f, --fail is used.
2662 if not quiet:
2663 print("Server does not provide clone.bundle; ignoring.",
2664 file=sys.stderr)
2665 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002666 elif curlret and not verbose and output:
2667 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002668
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002669 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002670 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002671 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002672 return True
2673 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002674 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002675 return False
2676 else:
2677 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002678
Kris Giesingc8d882a2014-12-23 13:02:32 -08002679 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002680 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002681 with open(path, 'rb') as f:
2682 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002683 return True
2684 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002685 if not quiet:
2686 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002687 return False
2688 except OSError:
2689 return False
2690
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002691 def _Checkout(self, rev, quiet=False):
2692 cmd = ['checkout']
2693 if quiet:
2694 cmd.append('-q')
2695 cmd.append(rev)
2696 cmd.append('--')
2697 if GitCommand(self, cmd).Wait() != 0:
2698 if self._allrefs:
2699 raise GitError('%s checkout %s ' % (self.name, rev))
2700
Anthony King7bdac712014-07-16 12:56:40 +01002701 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002702 cmd = ['cherry-pick']
2703 cmd.append(rev)
2704 cmd.append('--')
2705 if GitCommand(self, cmd).Wait() != 0:
2706 if self._allrefs:
2707 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2708
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302709 def _LsRemote(self, refs):
2710 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302711 p = GitCommand(self, cmd, capture_stdout=True)
2712 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002713 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302714 return None
2715
Anthony King7bdac712014-07-16 12:56:40 +01002716 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002717 cmd = ['revert']
2718 cmd.append('--no-edit')
2719 cmd.append(rev)
2720 cmd.append('--')
2721 if GitCommand(self, cmd).Wait() != 0:
2722 if self._allrefs:
2723 raise GitError('%s revert %s ' % (self.name, rev))
2724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725 def _ResetHard(self, rev, quiet=True):
2726 cmd = ['reset', '--hard']
2727 if quiet:
2728 cmd.append('-q')
2729 cmd.append(rev)
2730 if GitCommand(self, cmd).Wait() != 0:
2731 raise GitError('%s reset --hard %s ' % (self.name, rev))
2732
Martin Kellye4e94d22017-03-21 16:05:12 -07002733 def _SyncSubmodules(self, quiet=True):
2734 cmd = ['submodule', 'update', '--init', '--recursive']
2735 if quiet:
2736 cmd.append('-q')
2737 if GitCommand(self, cmd).Wait() != 0:
2738 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2739
Anthony King7bdac712014-07-16 12:56:40 +01002740 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002741 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002742 if onto is not None:
2743 cmd.extend(['--onto', onto])
2744 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002745 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002746 raise GitError('%s rebase %s ' % (self.name, upstream))
2747
Pierre Tardy3d125942012-05-04 12:18:12 +02002748 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002749 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002750 if ffonly:
2751 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002752 if GitCommand(self, cmd).Wait() != 0:
2753 raise GitError('%s merge %s ' % (self.name, head))
2754
David Pursehousee8ace262020-02-13 12:41:15 +09002755 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002756 init_git_dir = not os.path.exists(self.gitdir)
2757 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002758 try:
2759 # Initialize the bare repository, which contains all of the objects.
2760 if init_obj_dir:
2761 os.makedirs(self.objdir)
2762 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002763
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002764 # Enable per-worktree config file support if possible. This is more a
2765 # nice-to-have feature for users rather than a hard requirement.
2766 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002767 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002768
Kevin Degib1a07b82015-07-27 13:33:43 -06002769 # If we have a separate directory to hold refs, initialize it as well.
2770 if self.objdir != self.gitdir:
2771 if init_git_dir:
2772 os.makedirs(self.gitdir)
2773
2774 if init_obj_dir or init_git_dir:
2775 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2776 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002777 try:
2778 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2779 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002780 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002781 print("Retrying clone after deleting %s" %
2782 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002783 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002784 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2785 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002786 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002787 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002788 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2789 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002790 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002791 raise e
2792 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002793
Kevin Degi384b3c52014-10-16 16:02:58 -06002794 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002795 mp = self.manifest.manifestProject
2796 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002797
Kevin Degib1a07b82015-07-27 13:33:43 -06002798 if ref_dir or mirror_git:
2799 if not mirror_git:
2800 mirror_git = os.path.join(ref_dir, self.name + '.git')
2801 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2802 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002803 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2804 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002805
Kevin Degib1a07b82015-07-27 13:33:43 -06002806 if os.path.exists(mirror_git):
2807 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002808 elif os.path.exists(repo_git):
2809 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002810 elif os.path.exists(worktrees_git):
2811 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002812 else:
2813 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002814
Kevin Degib1a07b82015-07-27 13:33:43 -06002815 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002816 if not os.path.isabs(ref_dir):
2817 # The alternate directory is relative to the object database.
2818 ref_dir = os.path.relpath(ref_dir,
2819 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002820 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2821 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002822
David Pursehousee8ace262020-02-13 12:41:15 +09002823 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002824
2825 m = self.manifest.manifestProject.config
2826 for key in ['user.name', 'user.email']:
2827 if m.Has(key, include_defaults=False):
2828 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002829 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002830 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002831 if self.manifest.IsMirror:
2832 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002833 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002834 self.config.SetString('core.bare', None)
2835 except Exception:
2836 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002837 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002838 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002839 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002840 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002841
David Pursehousee8ace262020-02-13 12:41:15 +09002842 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002843 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002844 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002845
David Pursehousee8ace262020-02-13 12:41:15 +09002846 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002847 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002848 if not os.path.exists(hooks):
2849 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002850 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002851 name = os.path.basename(stock_hook)
2852
Victor Boivie65e0f352011-04-18 11:23:29 +02002853 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002854 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002855 # Don't install a Gerrit Code Review hook if this
2856 # project does not appear to use it for reviews.
2857 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002858 # Since the manifest project is one of those, but also
2859 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002860 continue
2861
2862 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002863 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002864 continue
2865 if os.path.exists(dst):
2866 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002867 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002868 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002869 if not quiet:
2870 _warn("%s: Not replacing locally modified %s hook",
2871 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002872 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002873 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002874 platform_utils.symlink(
2875 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002876 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002877 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002878 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002879 else:
2880 raise
2881
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002882 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002883 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002884 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002885 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002886 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002887 remote.review = self.remote.review
2888 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002889
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002890 if self.worktree:
2891 remote.ResetFetch(mirror=False)
2892 else:
2893 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002894 remote.Save()
2895
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002896 def _InitMRef(self):
2897 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002898 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002899
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002900 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002901 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002902
2903 def _InitAnyMRef(self, ref):
2904 cur = self.bare_ref.symref(ref)
2905
2906 if self.revisionId:
2907 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2908 msg = 'manifest set to %s' % self.revisionId
2909 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002910 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002911 else:
2912 remote = self.GetRemote(self.remote.name)
2913 dst = remote.ToLocal(self.revisionExpr)
2914 if cur != dst:
2915 msg = 'manifest set to %s' % self.revisionExpr
2916 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002917
Kevin Degi384b3c52014-10-16 16:02:58 -06002918 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002919 # Git worktrees don't use symlinks to share at all.
2920 if self.use_git_worktrees:
2921 return
2922
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002923 symlink_files = self.shareable_files[:]
2924 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002925 if share_refs:
2926 symlink_files += self.working_tree_files
2927 symlink_dirs += self.working_tree_dirs
2928 to_symlink = symlink_files + symlink_dirs
2929 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002930 # Try to self-heal a bit in simple cases.
2931 dst_path = os.path.join(destdir, name)
2932 src_path = os.path.join(srcdir, name)
2933
2934 if name in self.working_tree_dirs:
2935 # If the dir is missing under .repo/projects/, create it.
2936 if not os.path.exists(src_path):
2937 os.makedirs(src_path)
2938
2939 elif name in self.working_tree_files:
2940 # If it's a file under the checkout .git/ and the .repo/projects/ has
2941 # nothing, move the file under the .repo/projects/ tree.
2942 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2943 platform_utils.rename(dst_path, src_path)
2944
2945 # If the path exists under the .repo/projects/ and there's no symlink
2946 # under the checkout .git/, recreate the symlink.
2947 if name in self.working_tree_dirs or name in self.working_tree_files:
2948 if os.path.exists(src_path) and not os.path.exists(dst_path):
2949 platform_utils.symlink(
2950 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2951
2952 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002953 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002954 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002955 # Fail if the links are pointing to the wrong place
2956 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002957 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002958 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002959 'work tree. If you\'re comfortable with the '
2960 'possibility of losing the work tree\'s git metadata,'
2961 ' use `repo sync --force-sync {0}` to '
2962 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002963
David James8d201162013-10-11 17:03:19 -07002964 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2965 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2966
2967 Args:
2968 gitdir: The bare git repository. Must already be initialized.
2969 dotgit: The repository you would like to initialize.
2970 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2971 Only one work tree can store refs under a given |gitdir|.
2972 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2973 This saves you the effort of initializing |dotgit| yourself.
2974 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002975 symlink_files = self.shareable_files[:]
2976 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002977 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002978 symlink_files += self.working_tree_files
2979 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002980 to_symlink = symlink_files + symlink_dirs
2981
2982 to_copy = []
2983 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002984 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002985
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002986 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002987 for name in set(to_copy).union(to_symlink):
2988 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002989 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002990 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002991
Kevin Degi384b3c52014-10-16 16:02:58 -06002992 if os.path.lexists(dst):
2993 continue
David James8d201162013-10-11 17:03:19 -07002994
2995 # If the source dir doesn't exist, create an empty dir.
2996 if name in symlink_dirs and not os.path.lexists(src):
2997 os.makedirs(src)
2998
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002999 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07003000 platform_utils.symlink(
3001 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003002 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003003 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003004 shutil.copytree(src, dst)
3005 elif os.path.isfile(src):
3006 shutil.copy(src, dst)
3007
Conley Owens80b87fe2014-05-09 17:13:44 -07003008 # If the source file doesn't exist, ensure the destination
3009 # file doesn't either.
3010 if name in symlink_files and not os.path.lexists(src):
3011 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003012 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003013 except OSError:
3014 pass
3015
David James8d201162013-10-11 17:03:19 -07003016 except OSError as e:
3017 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003018 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003019 else:
3020 raise
3021
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003022 def _InitGitWorktree(self):
3023 """Init the project using git worktrees."""
3024 self.bare_git.worktree('prune')
3025 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3026 self.worktree, self.GetRevisionId())
3027
3028 # Rewrite the internal state files to use relative paths between the
3029 # checkouts & worktrees.
3030 dotgit = os.path.join(self.worktree, '.git')
3031 with open(dotgit, 'r') as fp:
3032 # Figure out the checkout->worktree path.
3033 setting = fp.read()
3034 assert setting.startswith('gitdir:')
3035 git_worktree_path = setting.split(':', 1)[1].strip()
3036 # Use relative path from checkout->worktree.
3037 with open(dotgit, 'w') as fp:
3038 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3039 file=fp)
3040 # Use relative path from worktree->checkout.
3041 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3042 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3043
Martin Kellye4e94d22017-03-21 16:05:12 -07003044 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003045 realdotgit = os.path.join(self.worktree, '.git')
3046 tmpdotgit = realdotgit + '.tmp'
3047 init_dotgit = not os.path.exists(realdotgit)
3048 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003049 if self.use_git_worktrees:
3050 self._InitGitWorktree()
3051 self._CopyAndLinkFiles()
3052 return
3053
Mike Frysingerf4545122019-11-11 04:34:16 -05003054 dotgit = tmpdotgit
3055 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3056 os.makedirs(tmpdotgit)
3057 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3058 copy_all=False)
3059 else:
3060 dotgit = realdotgit
3061
Kevin Degib1a07b82015-07-27 13:33:43 -06003062 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003063 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3064 except GitError as e:
3065 if force_sync and not init_dotgit:
3066 try:
3067 platform_utils.rmtree(dotgit)
3068 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003069 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003070 raise e
3071 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072
Mike Frysingerf4545122019-11-11 04:34:16 -05003073 if init_dotgit:
3074 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003075
Mike Frysingerf4545122019-11-11 04:34:16 -05003076 # Now that the .git dir is fully set up, move it to its final home.
3077 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003078
Mike Frysingerf4545122019-11-11 04:34:16 -05003079 # Finish checking out the worktree.
3080 cmd = ['read-tree', '--reset', '-u']
3081 cmd.append('-v')
3082 cmd.append(HEAD)
3083 if GitCommand(self, cmd).Wait() != 0:
3084 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003085
Mike Frysingerf4545122019-11-11 04:34:16 -05003086 if submodules:
3087 self._SyncSubmodules(quiet=True)
3088 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003089
Renaud Paquay788e9622017-01-27 11:41:12 -08003090 def _get_symlink_error_message(self):
3091 if platform_utils.isWindows():
3092 return ('Unable to create symbolic link. Please re-run the command as '
3093 'Administrator, or see '
3094 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3095 'for other options.')
3096 return 'filesystem must support symlinks'
3097
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003098 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003099 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003100
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003101 def _revlist(self, *args, **kw):
3102 a = []
3103 a.extend(args)
3104 a.append('--')
3105 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003106
3107 @property
3108 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003109 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003110
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003111 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003112 """Get logs between two revisions of this project."""
3113 comp = '..'
3114 if rev1:
3115 revs = [rev1]
3116 if rev2:
3117 revs.extend([comp, rev2])
3118 cmd = ['log', ''.join(revs)]
3119 out = DiffColoring(self.config)
3120 if out.is_on and color:
3121 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003122 if pretty_format is not None:
3123 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003124 if oneline:
3125 cmd.append('--oneline')
3126
3127 try:
3128 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3129 if log.Wait() == 0:
3130 return log.stdout
3131 except GitError:
3132 # worktree may not exist if groups changed for example. In that case,
3133 # try in gitdir instead.
3134 if not os.path.exists(self.worktree):
3135 return self.bare_git.log(*cmd[1:])
3136 else:
3137 raise
3138 return None
3139
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003140 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3141 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003142 """Get the list of logs from this revision to given revisionId"""
3143 logs = {}
3144 selfId = self.GetRevisionId(self._allrefs)
3145 toId = toProject.GetRevisionId(toProject._allrefs)
3146
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003147 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3148 pretty_format=pretty_format)
3149 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3150 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003151 return logs
3152
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003153 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003154
David James8d201162013-10-11 17:03:19 -07003155 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003156 self._project = project
3157 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003158 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003159
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003160 def LsOthers(self):
3161 p = GitCommand(self._project,
3162 ['ls-files',
3163 '-z',
3164 '--others',
3165 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003166 bare=False,
David James8d201162013-10-11 17:03:19 -07003167 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003168 capture_stdout=True,
3169 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003170 if p.Wait() == 0:
3171 out = p.stdout
3172 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003173 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003174 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003175 return []
3176
3177 def DiffZ(self, name, *args):
3178 cmd = [name]
3179 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003180 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003181 cmd.extend(args)
3182 p = GitCommand(self._project,
3183 cmd,
David James8d201162013-10-11 17:03:19 -07003184 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003185 bare=False,
3186 capture_stdout=True,
3187 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003188 try:
3189 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003190 if not hasattr(out, 'encode'):
3191 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003192 r = {}
3193 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003194 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003195 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003196 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003197 info = next(out)
3198 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003199 except StopIteration:
3200 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003201
3202 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003203
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003204 def __init__(self, path, omode, nmode, oid, nid, state):
3205 self.path = path
3206 self.src_path = None
3207 self.old_mode = omode
3208 self.new_mode = nmode
3209 self.old_id = oid
3210 self.new_id = nid
3211
3212 if len(state) == 1:
3213 self.status = state
3214 self.level = None
3215 else:
3216 self.status = state[:1]
3217 self.level = state[1:]
3218 while self.level.startswith('0'):
3219 self.level = self.level[1:]
3220
3221 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003222 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003223 if info.status in ('R', 'C'):
3224 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003225 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003226 r[info.path] = info
3227 return r
3228 finally:
3229 p.Wait()
3230
3231 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003232 if self._bare:
3233 path = os.path.join(self._project.gitdir, HEAD)
3234 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003235 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003236 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003237 with open(path) as fd:
3238 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003239 except IOError as e:
3240 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003241 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303242 line = line.decode()
3243 except AttributeError:
3244 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003245 if line.startswith('ref: '):
3246 return line[5:-1]
3247 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003248
3249 def SetHead(self, ref, message=None):
3250 cmdv = []
3251 if message is not None:
3252 cmdv.extend(['-m', message])
3253 cmdv.append(HEAD)
3254 cmdv.append(ref)
3255 self.symbolic_ref(*cmdv)
3256
3257 def DetachHead(self, new, message=None):
3258 cmdv = ['--no-deref']
3259 if message is not None:
3260 cmdv.extend(['-m', message])
3261 cmdv.append(HEAD)
3262 cmdv.append(new)
3263 self.update_ref(*cmdv)
3264
3265 def UpdateRef(self, name, new, old=None,
3266 message=None,
3267 detach=False):
3268 cmdv = []
3269 if message is not None:
3270 cmdv.extend(['-m', message])
3271 if detach:
3272 cmdv.append('--no-deref')
3273 cmdv.append(name)
3274 cmdv.append(new)
3275 if old is not None:
3276 cmdv.append(old)
3277 self.update_ref(*cmdv)
3278
3279 def DeleteRef(self, name, old=None):
3280 if not old:
3281 old = self.rev_parse(name)
3282 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003283 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003284
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003285 def rev_list(self, *args, **kw):
3286 if 'format' in kw:
3287 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3288 else:
3289 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003290 cmdv.extend(args)
3291 p = GitCommand(self._project,
3292 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003293 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003294 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003295 capture_stdout=True,
3296 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003297 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003298 raise GitError('%s rev-list %s: %s' %
3299 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003300 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003301
3302 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003303 """Allow arbitrary git commands using pythonic syntax.
3304
3305 This allows you to do things like:
3306 git_obj.rev_parse('HEAD')
3307
3308 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3309 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003310 Any other positional arguments will be passed to the git command, and the
3311 following keyword arguments are supported:
3312 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003313
3314 Args:
3315 name: The name of the git command to call. Any '_' characters will
3316 be replaced with '-'.
3317
3318 Returns:
3319 A callable object that will try to call git with the named command.
3320 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003321 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003322
Dave Borowitz091f8932012-10-23 17:01:04 -07003323 def runner(*args, **kwargs):
3324 cmdv = []
3325 config = kwargs.pop('config', None)
3326 for k in kwargs:
3327 raise TypeError('%s() got an unexpected keyword argument %r'
3328 % (name, k))
3329 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303330 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003331 cmdv.append('-c')
3332 cmdv.append('%s=%s' % (k, v))
3333 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003334 cmdv.extend(args)
3335 p = GitCommand(self._project,
3336 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003337 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003338 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003339 capture_stdout=True,
3340 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003341 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003342 raise GitError('%s %s: %s' %
3343 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003344 r = p.stdout
3345 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3346 return r[:-1]
3347 return r
3348 return runner
3349
3350
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003351class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003352
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003353 def __str__(self):
3354 return 'prior sync failed; rebase still in progress'
3355
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003356
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003357class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003358
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003359 def __str__(self):
3360 return 'contains uncommitted changes'
3361
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003362
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003363class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003364
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003365 def __init__(self, project, text):
3366 self.project = project
3367 self.text = text
3368
3369 def Print(self, syncbuf):
3370 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3371 syncbuf.out.nl()
3372
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003373
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003374class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003375
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003376 def __init__(self, project, why):
3377 self.project = project
3378 self.why = why
3379
3380 def Print(self, syncbuf):
3381 syncbuf.out.fail('error: %s/: %s',
3382 self.project.relpath,
3383 str(self.why))
3384 syncbuf.out.nl()
3385
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003386
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003387class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003388
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003389 def __init__(self, project, action):
3390 self.project = project
3391 self.action = action
3392
3393 def Run(self, syncbuf):
3394 out = syncbuf.out
3395 out.project('project %s/', self.project.relpath)
3396 out.nl()
3397 try:
3398 self.action()
3399 out.nl()
3400 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003401 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003402 out.nl()
3403 return False
3404
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003405
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003406class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003407
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003408 def __init__(self, config):
3409 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003410 self.project = self.printer('header', attr='bold')
3411 self.info = self.printer('info')
3412 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003413
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003414
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003415class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003416
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003417 def __init__(self, config, detach_head=False):
3418 self._messages = []
3419 self._failures = []
3420 self._later_queue1 = []
3421 self._later_queue2 = []
3422
3423 self.out = _SyncColoring(config)
3424 self.out.redirect(sys.stderr)
3425
3426 self.detach_head = detach_head
3427 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003428 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003429
3430 def info(self, project, fmt, *args):
3431 self._messages.append(_InfoMessage(project, fmt % args))
3432
3433 def fail(self, project, err=None):
3434 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003435 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003436
3437 def later1(self, project, what):
3438 self._later_queue1.append(_Later(project, what))
3439
3440 def later2(self, project, what):
3441 self._later_queue2.append(_Later(project, what))
3442
3443 def Finish(self):
3444 self._PrintMessages()
3445 self._RunLater()
3446 self._PrintMessages()
3447 return self.clean
3448
David Rileye0684ad2017-04-05 00:02:59 -07003449 def Recently(self):
3450 recent_clean = self.recent_clean
3451 self.recent_clean = True
3452 return recent_clean
3453
3454 def _MarkUnclean(self):
3455 self.clean = False
3456 self.recent_clean = False
3457
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003458 def _RunLater(self):
3459 for q in ['_later_queue1', '_later_queue2']:
3460 if not self._RunQueue(q):
3461 return
3462
3463 def _RunQueue(self, queue):
3464 for m in getattr(self, queue):
3465 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003466 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003467 return False
3468 setattr(self, queue, [])
3469 return True
3470
3471 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003472 if self._messages or self._failures:
3473 if os.isatty(2):
3474 self.out.write(progress.CSI_ERASE_LINE)
3475 self.out.write('\r')
3476
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003477 for m in self._messages:
3478 m.Print(self)
3479 for m in self._failures:
3480 m.Print(self)
3481
3482 self._messages = []
3483 self._failures = []
3484
3485
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003486class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003487
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003488 """A special project housed under .repo.
3489 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003490
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003491 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003492 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003493 manifest=manifest,
3494 name=name,
3495 gitdir=gitdir,
3496 objdir=gitdir,
3497 worktree=worktree,
3498 remote=RemoteSpec('origin'),
3499 relpath='.repo/%s' % name,
3500 revisionExpr='refs/heads/master',
3501 revisionId=None,
3502 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003503
3504 def PreSync(self):
3505 if self.Exists:
3506 cb = self.CurrentBranch
3507 if cb:
3508 base = self.GetBranch(cb).merge
3509 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003510 self.revisionExpr = base
3511 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003512
Martin Kelly224a31a2017-07-10 14:46:25 -07003513 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003514 """ Prepare MetaProject for manifest branch switch
3515 """
3516
3517 # detach and delete manifest branch, allowing a new
3518 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003519 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003520 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003521 syncbuf.Finish()
3522
3523 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003524 ['update-ref', '-d', 'refs/heads/default'],
3525 capture_stdout=True,
3526 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003528 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003529 def LastFetch(self):
3530 try:
3531 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3532 return os.path.getmtime(fh)
3533 except OSError:
3534 return 0
3535
3536 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003537 def HasChanges(self):
3538 """Has the remote received new commits not yet checked out?
3539 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003540 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003541 return False
3542
David Pursehouse8a68ff92012-09-24 12:15:13 +09003543 all_refs = self.bare_ref.all
3544 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003545 head = self.work_git.GetHead()
3546 if head.startswith(R_HEADS):
3547 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003548 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003549 except KeyError:
3550 head = None
3551
3552 if revid == head:
3553 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003554 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003555 return True
3556 return False