blob: 5bead641d2dd404ae9d4cb70e823283aa55a3367 [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
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001835# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001836 def GetHeadPath(self):
1837 """Return the full path to the HEAD ref."""
1838 dotgit = os.path.join(self.worktree, '.git')
1839 if os.path.isfile(dotgit):
1840 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1841 with open(dotgit) as fp:
1842 setting = fp.read()
1843 assert setting.startswith('gitdir:')
1844 gitdir = setting.split(':', 1)[1].strip()
1845 dotgit = os.path.join(self.worktree, gitdir)
1846 return os.path.join(dotgit, HEAD)
1847
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001848 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001849 """Create a new branch off the manifest's revision.
1850 """
Simran Basib9a1b732015-08-20 12:19:28 -07001851 if not branch_merge:
1852 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001853 head = self.work_git.GetHead()
1854 if head == (R_HEADS + name):
1855 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001856
David Pursehouse8a68ff92012-09-24 12:15:13 +09001857 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001858 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001859 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001860 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001861 capture_stdout=True,
1862 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001863
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001864 branch = self.GetBranch(name)
1865 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001866 branch.merge = branch_merge
1867 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1868 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001869
1870 if revision is None:
1871 revid = self.GetRevisionId(all_refs)
1872 else:
1873 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001874
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001875 if head.startswith(R_HEADS):
1876 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001877 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001878 except KeyError:
1879 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001880 if revid and head and revid == head:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05001881 if self.use_git_worktrees:
1882 self.work_git.update_ref(HEAD, revid)
1883 branch.Save()
1884 else:
1885 ref = os.path.join(self.gitdir, R_HEADS + name)
1886 try:
1887 os.makedirs(os.path.dirname(ref))
1888 except OSError:
1889 pass
1890 _lwrite(ref, '%s\n' % revid)
1891 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
1892 branch.Save()
1893 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001894
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001895 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001896 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001897 capture_stdout=True,
1898 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001899 branch.Save()
1900 return True
1901 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001902
Wink Saville02d79452009-04-10 13:01:24 -07001903 def CheckoutBranch(self, name):
1904 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001905
1906 Args:
1907 name: The name of the branch to checkout.
1908
1909 Returns:
1910 True if the checkout succeeded; False if it didn't; None if the branch
1911 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001912 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001913 rev = R_HEADS + name
1914 head = self.work_git.GetHead()
1915 if head == rev:
1916 # Already on the branch
1917 #
1918 return True
Wink Saville02d79452009-04-10 13:01:24 -07001919
David Pursehouse8a68ff92012-09-24 12:15:13 +09001920 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001921 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001922 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001923 except KeyError:
1924 # Branch does not exist in this project
1925 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001926 return None
Wink Saville02d79452009-04-10 13:01:24 -07001927
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001928 if head.startswith(R_HEADS):
1929 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001930 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001931 except KeyError:
1932 head = None
1933
1934 if head == revid:
1935 # Same revision; just update HEAD to point to the new
1936 # target branch, but otherwise take no other action.
1937 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001938 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001939 return True
1940
1941 return GitCommand(self,
1942 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001943 capture_stdout=True,
1944 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001945
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001946 def AbandonBranch(self, name):
1947 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001948
1949 Args:
1950 name: The name of the branch to abandon.
1951
1952 Returns:
1953 True if the abandon succeeded; False if it didn't; None if the branch
1954 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001955 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001956 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001957 all_refs = self.bare_ref.all
1958 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001959 # Doesn't exist
1960 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001961
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001962 head = self.work_git.GetHead()
1963 if head == rev:
1964 # We can't destroy the branch while we are sitting
1965 # on it. Switch to a detached HEAD.
1966 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001967 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001968
David Pursehouse8a68ff92012-09-24 12:15:13 +09001969 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001970 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05001971 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001972 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001973 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001974
1975 return GitCommand(self,
1976 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001977 capture_stdout=True,
1978 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001979
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001980 def PruneHeads(self):
1981 """Prune any topic branches already merged into upstream.
1982 """
1983 cb = self.CurrentBranch
1984 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001985 left = self._allrefs
1986 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001987 if name.startswith(R_HEADS):
1988 name = name[len(R_HEADS):]
1989 if cb is None or name != cb:
1990 kill.append(name)
1991
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001992 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993 if cb is not None \
1994 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001995 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996 self.work_git.DetachHead(HEAD)
1997 kill.append(cb)
1998
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001999 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002000 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002001
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002002 try:
2003 self.bare_git.DetachHead(rev)
2004
2005 b = ['branch', '-d']
2006 b.extend(kill)
2007 b = GitCommand(self, b, bare=True,
2008 capture_stdout=True,
2009 capture_stderr=True)
2010 b.Wait()
2011 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002012 if ID_RE.match(old):
2013 self.bare_git.DetachHead(old)
2014 else:
2015 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002016 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002018 for branch in kill:
2019 if (R_HEADS + branch) not in left:
2020 self.CleanPublishedCache()
2021 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002022
2023 if cb and cb not in kill:
2024 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002025 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002026
2027 kept = []
2028 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002029 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002030 branch = self.GetBranch(branch)
2031 base = branch.LocalMerge
2032 if not base:
2033 base = rev
2034 kept.append(ReviewableBranch(self, branch, base))
2035 return kept
2036
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002037# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002038 def GetRegisteredSubprojects(self):
2039 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002040
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002041 def rec(subprojects):
2042 if not subprojects:
2043 return
2044 result.extend(subprojects)
2045 for p in subprojects:
2046 rec(p.subprojects)
2047 rec(self.subprojects)
2048 return result
2049
2050 def _GetSubmodules(self):
2051 # Unfortunately we cannot call `git submodule status --recursive` here
2052 # because the working tree might not exist yet, and it cannot be used
2053 # without a working tree in its current implementation.
2054
2055 def get_submodules(gitdir, rev):
2056 # Parse .gitmodules for submodule sub_paths and sub_urls
2057 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2058 if not sub_paths:
2059 return []
2060 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2061 # revision of submodule repository
2062 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2063 submodules = []
2064 for sub_path, sub_url in zip(sub_paths, sub_urls):
2065 try:
2066 sub_rev = sub_revs[sub_path]
2067 except KeyError:
2068 # Ignore non-exist submodules
2069 continue
2070 submodules.append((sub_rev, sub_path, sub_url))
2071 return submodules
2072
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002073 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2074 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002075
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002076 def parse_gitmodules(gitdir, rev):
2077 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2078 try:
Anthony King7bdac712014-07-16 12:56:40 +01002079 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2080 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002081 except GitError:
2082 return [], []
2083 if p.Wait() != 0:
2084 return [], []
2085
2086 gitmodules_lines = []
2087 fd, temp_gitmodules_path = tempfile.mkstemp()
2088 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002089 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002090 os.close(fd)
2091 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002092 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2093 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002094 if p.Wait() != 0:
2095 return [], []
2096 gitmodules_lines = p.stdout.split('\n')
2097 except GitError:
2098 return [], []
2099 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002100 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002101
2102 names = set()
2103 paths = {}
2104 urls = {}
2105 for line in gitmodules_lines:
2106 if not line:
2107 continue
2108 m = re_path.match(line)
2109 if m:
2110 names.add(m.group(1))
2111 paths[m.group(1)] = m.group(2)
2112 continue
2113 m = re_url.match(line)
2114 if m:
2115 names.add(m.group(1))
2116 urls[m.group(1)] = m.group(2)
2117 continue
2118 names = sorted(names)
2119 return ([paths.get(name, '') for name in names],
2120 [urls.get(name, '') for name in names])
2121
2122 def git_ls_tree(gitdir, rev, paths):
2123 cmd = ['ls-tree', rev, '--']
2124 cmd.extend(paths)
2125 try:
Anthony King7bdac712014-07-16 12:56:40 +01002126 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2127 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002128 except GitError:
2129 return []
2130 if p.Wait() != 0:
2131 return []
2132 objects = {}
2133 for line in p.stdout.split('\n'):
2134 if not line.strip():
2135 continue
2136 object_rev, object_path = line.split()[2:4]
2137 objects[object_path] = object_rev
2138 return objects
2139
2140 try:
2141 rev = self.GetRevisionId()
2142 except GitError:
2143 return []
2144 return get_submodules(self.gitdir, rev)
2145
2146 def GetDerivedSubprojects(self):
2147 result = []
2148 if not self.Exists:
2149 # If git repo does not exist yet, querying its submodules will
2150 # mess up its states; so return here.
2151 return result
2152 for rev, path, url in self._GetSubmodules():
2153 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002154 relpath, worktree, gitdir, objdir = \
2155 self.manifest.GetSubprojectPaths(self, name, path)
2156 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002157 if project:
2158 result.extend(project.GetDerivedSubprojects())
2159 continue
David James8d201162013-10-11 17:03:19 -07002160
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002161 if url.startswith('..'):
2162 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002163 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002164 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002165 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002166 review=self.remote.review,
2167 revision=self.remote.revision)
2168 subproject = Project(manifest=self.manifest,
2169 name=name,
2170 remote=remote,
2171 gitdir=gitdir,
2172 objdir=objdir,
2173 worktree=worktree,
2174 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002175 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002176 revisionId=rev,
2177 rebase=self.rebase,
2178 groups=self.groups,
2179 sync_c=self.sync_c,
2180 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002181 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002182 parent=self,
2183 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002184 result.append(subproject)
2185 result.extend(subproject.GetDerivedSubprojects())
2186 return result
2187
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002188# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002189 def EnableRepositoryExtension(self, key, value='true', version=1):
2190 """Enable git repository extension |key| with |value|.
2191
2192 Args:
2193 key: The extension to enabled. Omit the "extensions." prefix.
2194 value: The value to use for the extension.
2195 version: The minimum git repository version needed.
2196 """
2197 # Make sure the git repo version is new enough already.
2198 found_version = self.config.GetInt('core.repositoryFormatVersion')
2199 if found_version is None:
2200 found_version = 0
2201 if found_version < version:
2202 self.config.SetString('core.repositoryFormatVersion', str(version))
2203
2204 # Enable the extension!
2205 self.config.SetString('extensions.%s' % (key,), value)
2206
Zac Livingstone4332262017-06-16 08:56:09 -06002207 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002208 try:
2209 # if revision (sha or tag) is not present then following function
2210 # throws an error.
2211 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2212 return True
2213 except GitError:
2214 # There is no such persistent revision. We have to fetch it.
2215 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002216
Julien Campergue335f5ef2013-10-16 11:02:35 +02002217 def _FetchArchive(self, tarpath, cwd=None):
2218 cmd = ['archive', '-v', '-o', tarpath]
2219 cmd.append('--remote=%s' % self.remote.url)
2220 cmd.append('--prefix=%s/' % self.relpath)
2221 cmd.append(self.revisionExpr)
2222
2223 command = GitCommand(self, cmd, cwd=cwd,
2224 capture_stdout=True,
2225 capture_stderr=True)
2226
2227 if command.Wait() != 0:
2228 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2229
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002230 def _RemoteFetch(self, name=None,
2231 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002232 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002233 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002234 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002235 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002236 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002237 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002238 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002239 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002240 force_sync=False,
2241 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002242
2243 is_sha1 = False
2244 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002245 # The depth should not be used when fetching to a mirror because
2246 # it will result in a shallow repository that cannot be cloned or
2247 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002248 # The repo project should also never be synced with partial depth.
2249 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2250 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002251
Shawn Pearce69e04d82014-01-29 12:48:54 -08002252 if depth:
2253 current_branch_only = True
2254
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002255 if ID_RE.match(self.revisionExpr) is not None:
2256 is_sha1 = True
2257
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002258 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002259 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002260 # this is a tag and its sha1 value should never change
2261 tag_name = self.revisionExpr[len(R_TAGS):]
2262
2263 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002264 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002265 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002266 print('Skipped fetching project %s (already have persistent ref)'
2267 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002268 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002269 if is_sha1 and not depth:
2270 # When syncing a specific commit and --depth is not set:
2271 # * if upstream is explicitly specified and is not a sha1, fetch only
2272 # upstream as users expect only upstream to be fetch.
2273 # Note: The commit might not be in upstream in which case the sync
2274 # will fail.
2275 # * otherwise, fetch all branches to make sure we end up with the
2276 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002277 if self.upstream:
2278 current_branch_only = not ID_RE.match(self.upstream)
2279 else:
2280 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002281
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002282 if not name:
2283 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002284
2285 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002286 remote = self.GetRemote(name)
2287 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002288 ssh_proxy = True
2289
Shawn O. Pearce88443382010-10-08 10:02:09 +02002290 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002291 if alt_dir and 'objects' == os.path.basename(alt_dir):
2292 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2294 remote = self.GetRemote(name)
2295
David Pursehouse8a68ff92012-09-24 12:15:13 +09002296 all_refs = self.bare_ref.all
2297 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002298 tmp = set()
2299
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302300 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002301 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002302 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002303 all_refs[r] = ref_id
2304 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002305 continue
2306
David Pursehouse8a68ff92012-09-24 12:15:13 +09002307 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002308 continue
2309
David Pursehouse8a68ff92012-09-24 12:15:13 +09002310 r = 'refs/_alt/%s' % ref_id
2311 all_refs[r] = ref_id
2312 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002313 tmp.add(r)
2314
heping3d7bbc92017-04-12 19:51:47 +08002315 tmp_packed_lines = []
2316 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002317
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302318 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002319 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002320 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002321 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002322 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002323
heping3d7bbc92017-04-12 19:51:47 +08002324 tmp_packed = ''.join(tmp_packed_lines)
2325 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002326 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002327 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002328 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002329
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002330 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002331
Xin Li745be2e2019-06-03 11:24:30 -07002332 if clone_filter:
2333 git_require((2, 19, 0), fail=True, msg='partial clones')
2334 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002335 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002336
Conley Owensf97e8382015-01-21 11:12:46 -08002337 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002338 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002339 else:
2340 # If this repo has shallow objects, then we don't know which refs have
2341 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2342 # do this with projects that don't have shallow objects, since it is less
2343 # efficient.
2344 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2345 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002346
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002347 if quiet:
2348 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002349 if not self.worktree:
2350 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002351 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002352
Mike Frysingere57f1142019-03-18 21:27:54 -04002353 if force_sync:
2354 cmd.append('--force')
2355
David Pursehouse74cfd272015-10-14 10:50:15 +09002356 if prune:
2357 cmd.append('--prune')
2358
Martin Kellye4e94d22017-03-21 16:05:12 -07002359 if submodules:
2360 cmd.append('--recurse-submodules=on-demand')
2361
Kuang-che Wu6856f982019-11-25 12:37:55 +08002362 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002363 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002364 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002365 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002366 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002367 spec.append('tag')
2368 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002369
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302370 if self.manifest.IsMirror and not current_branch_only:
2371 branch = None
2372 else:
2373 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002374 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002375 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002376 # Shallow checkout of a specific commit, fetch from that commit and not
2377 # the heads only as the commit might be deeper in the history.
2378 spec.append(branch)
2379 else:
2380 if is_sha1:
2381 branch = self.upstream
2382 if branch is not None and branch.strip():
2383 if not branch.startswith('refs/'):
2384 branch = R_HEADS + branch
2385 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2386
2387 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2388 # whole repo.
2389 if self.manifest.IsMirror and not spec:
2390 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2391
2392 # If using depth then we should not get all the tags since they may
2393 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002394 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002395 cmd.append('--no-tags')
2396 else:
2397 cmd.append('--tags')
2398 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2399
Conley Owens80b87fe2014-05-09 17:13:44 -07002400 cmd.extend(spec)
2401
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002402 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002403 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002404 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2405 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002406 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002407 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002408 ok = True
2409 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002410 # If needed, run the 'git remote prune' the first time through the loop
2411 elif (not _i and
2412 "error:" in gitcmd.stderr and
2413 "git remote prune" in gitcmd.stderr):
2414 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002415 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002416 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002417 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002418 break
2419 continue
Brian Harring14a66742012-09-28 20:21:57 -07002420 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002421 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2422 # in sha1 mode, we just tried sync'ing from the upstream field; it
2423 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002424 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002425 elif ret < 0:
2426 # Git died with a signal, exit immediately
2427 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002428 if not verbose:
2429 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002430 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002431
2432 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002433 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002434 if old_packed != '':
2435 _lwrite(packed_refs, old_packed)
2436 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002437 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002438 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002439
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002440 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002441 # We just synced the upstream given branch; verify we
2442 # got what we wanted, else trigger a second run of all
2443 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002444 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002445 # Sync the current branch only with depth set to None.
2446 # We always pass depth=None down to avoid infinite recursion.
2447 return self._RemoteFetch(
2448 name=name, quiet=quiet, verbose=verbose,
2449 current_branch_only=current_branch_only and depth,
2450 initial=False, alt_dir=alt_dir,
2451 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002452
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002453 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002454
Mike Frysingere50b6a72020-02-19 01:45:48 -05002455 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002456 if initial and \
2457 (self.manifest.manifestProject.config.GetString('repo.depth') or
2458 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002459 return False
2460
2461 remote = self.GetRemote(self.remote.name)
2462 bundle_url = remote.url + '/clone.bundle'
2463 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002464 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2465 'persistent-http',
2466 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002467 return False
2468
2469 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2470 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2471
2472 exist_dst = os.path.exists(bundle_dst)
2473 exist_tmp = os.path.exists(bundle_tmp)
2474
2475 if not initial and not exist_dst and not exist_tmp:
2476 return False
2477
2478 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002479 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2480 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002481 if not exist_dst:
2482 return False
2483
2484 cmd = ['fetch']
2485 if quiet:
2486 cmd.append('--quiet')
2487 if not self.worktree:
2488 cmd.append('--update-head-ok')
2489 cmd.append(bundle_dst)
2490 for f in remote.fetch:
2491 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002492 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002493
2494 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002495 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002496 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002497 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002498 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002499 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002500
Mike Frysingere50b6a72020-02-19 01:45:48 -05002501 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002502 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002503 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002504
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002505 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002506 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002507 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002508 if os.path.exists(tmpPath):
2509 size = os.stat(tmpPath).st_size
2510 if size >= 1024:
2511 cmd += ['--continue-at', '%d' % (size,)]
2512 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002513 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002514 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002515 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002516 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002517 if proxy:
2518 cmd += ['--proxy', proxy]
2519 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2520 cmd += ['--proxy', os.environ['http_proxy']]
2521 if srcUrl.startswith('persistent-https'):
2522 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2523 elif srcUrl.startswith('persistent-http'):
2524 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002525 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002526
Dave Borowitz137d0132015-01-02 11:12:54 -08002527 if IsTrace():
2528 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002529 if verbose:
2530 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2531 stdout = None if verbose else subprocess.PIPE
2532 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002533 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002534 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002535 except OSError:
2536 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002537
Mike Frysingere50b6a72020-02-19 01:45:48 -05002538 (output, _) = proc.communicate()
2539 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002540
Dave Borowitz137d0132015-01-02 11:12:54 -08002541 if curlret == 22:
2542 # From curl man page:
2543 # 22: HTTP page not retrieved. The requested url was not found or
2544 # returned another error with the HTTP error code being 400 or above.
2545 # This return code only appears if -f, --fail is used.
2546 if not quiet:
2547 print("Server does not provide clone.bundle; ignoring.",
2548 file=sys.stderr)
2549 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002550 elif curlret and not verbose and output:
2551 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002552
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002553 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002554 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002555 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002556 return True
2557 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002558 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002559 return False
2560 else:
2561 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002562
Kris Giesingc8d882a2014-12-23 13:02:32 -08002563 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002564 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002565 with open(path, 'rb') as f:
2566 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002567 return True
2568 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002569 if not quiet:
2570 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002571 return False
2572 except OSError:
2573 return False
2574
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002575 def _Checkout(self, rev, quiet=False):
2576 cmd = ['checkout']
2577 if quiet:
2578 cmd.append('-q')
2579 cmd.append(rev)
2580 cmd.append('--')
2581 if GitCommand(self, cmd).Wait() != 0:
2582 if self._allrefs:
2583 raise GitError('%s checkout %s ' % (self.name, rev))
2584
Anthony King7bdac712014-07-16 12:56:40 +01002585 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002586 cmd = ['cherry-pick']
2587 cmd.append(rev)
2588 cmd.append('--')
2589 if GitCommand(self, cmd).Wait() != 0:
2590 if self._allrefs:
2591 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2592
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302593 def _LsRemote(self, refs):
2594 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302595 p = GitCommand(self, cmd, capture_stdout=True)
2596 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002597 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302598 return None
2599
Anthony King7bdac712014-07-16 12:56:40 +01002600 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002601 cmd = ['revert']
2602 cmd.append('--no-edit')
2603 cmd.append(rev)
2604 cmd.append('--')
2605 if GitCommand(self, cmd).Wait() != 0:
2606 if self._allrefs:
2607 raise GitError('%s revert %s ' % (self.name, rev))
2608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002609 def _ResetHard(self, rev, quiet=True):
2610 cmd = ['reset', '--hard']
2611 if quiet:
2612 cmd.append('-q')
2613 cmd.append(rev)
2614 if GitCommand(self, cmd).Wait() != 0:
2615 raise GitError('%s reset --hard %s ' % (self.name, rev))
2616
Martin Kellye4e94d22017-03-21 16:05:12 -07002617 def _SyncSubmodules(self, quiet=True):
2618 cmd = ['submodule', 'update', '--init', '--recursive']
2619 if quiet:
2620 cmd.append('-q')
2621 if GitCommand(self, cmd).Wait() != 0:
2622 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2623
Anthony King7bdac712014-07-16 12:56:40 +01002624 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002625 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002626 if onto is not None:
2627 cmd.extend(['--onto', onto])
2628 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002629 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630 raise GitError('%s rebase %s ' % (self.name, upstream))
2631
Pierre Tardy3d125942012-05-04 12:18:12 +02002632 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002633 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002634 if ffonly:
2635 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636 if GitCommand(self, cmd).Wait() != 0:
2637 raise GitError('%s merge %s ' % (self.name, head))
2638
David Pursehousee8ace262020-02-13 12:41:15 +09002639 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002640 init_git_dir = not os.path.exists(self.gitdir)
2641 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002642 try:
2643 # Initialize the bare repository, which contains all of the objects.
2644 if init_obj_dir:
2645 os.makedirs(self.objdir)
2646 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002647
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002648 # Enable per-worktree config file support if possible. This is more a
2649 # nice-to-have feature for users rather than a hard requirement.
2650 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002651 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002652
Kevin Degib1a07b82015-07-27 13:33:43 -06002653 # If we have a separate directory to hold refs, initialize it as well.
2654 if self.objdir != self.gitdir:
2655 if init_git_dir:
2656 os.makedirs(self.gitdir)
2657
2658 if init_obj_dir or init_git_dir:
2659 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2660 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002661 try:
2662 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2663 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002664 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002665 print("Retrying clone after deleting %s" %
2666 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002667 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002668 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2669 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002670 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002671 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002672 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2673 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002674 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002675 raise e
2676 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002677
Kevin Degi384b3c52014-10-16 16:02:58 -06002678 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002679 mp = self.manifest.manifestProject
2680 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002681
Kevin Degib1a07b82015-07-27 13:33:43 -06002682 if ref_dir or mirror_git:
2683 if not mirror_git:
2684 mirror_git = os.path.join(ref_dir, self.name + '.git')
2685 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2686 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002687 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2688 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002689
Kevin Degib1a07b82015-07-27 13:33:43 -06002690 if os.path.exists(mirror_git):
2691 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002692 elif os.path.exists(repo_git):
2693 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002694 elif os.path.exists(worktrees_git):
2695 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002696 else:
2697 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002698
Kevin Degib1a07b82015-07-27 13:33:43 -06002699 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002700 if not os.path.isabs(ref_dir):
2701 # The alternate directory is relative to the object database.
2702 ref_dir = os.path.relpath(ref_dir,
2703 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002704 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2705 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002706
David Pursehousee8ace262020-02-13 12:41:15 +09002707 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002708
2709 m = self.manifest.manifestProject.config
2710 for key in ['user.name', 'user.email']:
2711 if m.Has(key, include_defaults=False):
2712 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002713 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002714 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002715 if self.manifest.IsMirror:
2716 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002717 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002718 self.config.SetString('core.bare', None)
2719 except Exception:
2720 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002721 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002722 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002723 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002724 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725
David Pursehousee8ace262020-02-13 12:41:15 +09002726 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002727 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002728 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002729
David Pursehousee8ace262020-02-13 12:41:15 +09002730 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002731 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002732 if not os.path.exists(hooks):
2733 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002734 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002735 name = os.path.basename(stock_hook)
2736
Victor Boivie65e0f352011-04-18 11:23:29 +02002737 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002738 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002739 # Don't install a Gerrit Code Review hook if this
2740 # project does not appear to use it for reviews.
2741 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002742 # Since the manifest project is one of those, but also
2743 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002744 continue
2745
2746 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002747 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002748 continue
2749 if os.path.exists(dst):
2750 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002751 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002752 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002753 if not quiet:
2754 _warn("%s: Not replacing locally modified %s hook",
2755 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002756 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002757 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002758 platform_utils.symlink(
2759 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002760 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002761 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002762 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002763 else:
2764 raise
2765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002766 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002767 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002769 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002770 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002771 remote.review = self.remote.review
2772 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002773
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002774 if self.worktree:
2775 remote.ResetFetch(mirror=False)
2776 else:
2777 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002778 remote.Save()
2779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002780 def _InitMRef(self):
2781 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002782 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002783
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002784 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002785 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002786
2787 def _InitAnyMRef(self, ref):
2788 cur = self.bare_ref.symref(ref)
2789
2790 if self.revisionId:
2791 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2792 msg = 'manifest set to %s' % self.revisionId
2793 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002794 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002795 else:
2796 remote = self.GetRemote(self.remote.name)
2797 dst = remote.ToLocal(self.revisionExpr)
2798 if cur != dst:
2799 msg = 'manifest set to %s' % self.revisionExpr
2800 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002801
Kevin Degi384b3c52014-10-16 16:02:58 -06002802 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002803 # Git worktrees don't use symlinks to share at all.
2804 if self.use_git_worktrees:
2805 return
2806
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002807 symlink_files = self.shareable_files[:]
2808 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002809 if share_refs:
2810 symlink_files += self.working_tree_files
2811 symlink_dirs += self.working_tree_dirs
2812 to_symlink = symlink_files + symlink_dirs
2813 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002814 # Try to self-heal a bit in simple cases.
2815 dst_path = os.path.join(destdir, name)
2816 src_path = os.path.join(srcdir, name)
2817
2818 if name in self.working_tree_dirs:
2819 # If the dir is missing under .repo/projects/, create it.
2820 if not os.path.exists(src_path):
2821 os.makedirs(src_path)
2822
2823 elif name in self.working_tree_files:
2824 # If it's a file under the checkout .git/ and the .repo/projects/ has
2825 # nothing, move the file under the .repo/projects/ tree.
2826 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2827 platform_utils.rename(dst_path, src_path)
2828
2829 # If the path exists under the .repo/projects/ and there's no symlink
2830 # under the checkout .git/, recreate the symlink.
2831 if name in self.working_tree_dirs or name in self.working_tree_files:
2832 if os.path.exists(src_path) and not os.path.exists(dst_path):
2833 platform_utils.symlink(
2834 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2835
2836 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002837 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002838 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002839 # Fail if the links are pointing to the wrong place
2840 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002841 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002842 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002843 'work tree. If you\'re comfortable with the '
2844 'possibility of losing the work tree\'s git metadata,'
2845 ' use `repo sync --force-sync {0}` to '
2846 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002847
David James8d201162013-10-11 17:03:19 -07002848 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2849 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2850
2851 Args:
2852 gitdir: The bare git repository. Must already be initialized.
2853 dotgit: The repository you would like to initialize.
2854 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2855 Only one work tree can store refs under a given |gitdir|.
2856 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2857 This saves you the effort of initializing |dotgit| yourself.
2858 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002859 symlink_files = self.shareable_files[:]
2860 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002861 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002862 symlink_files += self.working_tree_files
2863 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002864 to_symlink = symlink_files + symlink_dirs
2865
2866 to_copy = []
2867 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002868 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002869
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002870 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002871 for name in set(to_copy).union(to_symlink):
2872 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002873 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002874 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002875
Kevin Degi384b3c52014-10-16 16:02:58 -06002876 if os.path.lexists(dst):
2877 continue
David James8d201162013-10-11 17:03:19 -07002878
2879 # If the source dir doesn't exist, create an empty dir.
2880 if name in symlink_dirs and not os.path.lexists(src):
2881 os.makedirs(src)
2882
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002883 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002884 platform_utils.symlink(
2885 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002886 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002887 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002888 shutil.copytree(src, dst)
2889 elif os.path.isfile(src):
2890 shutil.copy(src, dst)
2891
Conley Owens80b87fe2014-05-09 17:13:44 -07002892 # If the source file doesn't exist, ensure the destination
2893 # file doesn't either.
2894 if name in symlink_files and not os.path.lexists(src):
2895 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002896 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002897 except OSError:
2898 pass
2899
David James8d201162013-10-11 17:03:19 -07002900 except OSError as e:
2901 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002902 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002903 else:
2904 raise
2905
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002906 def _InitGitWorktree(self):
2907 """Init the project using git worktrees."""
2908 self.bare_git.worktree('prune')
2909 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2910 self.worktree, self.GetRevisionId())
2911
2912 # Rewrite the internal state files to use relative paths between the
2913 # checkouts & worktrees.
2914 dotgit = os.path.join(self.worktree, '.git')
2915 with open(dotgit, 'r') as fp:
2916 # Figure out the checkout->worktree path.
2917 setting = fp.read()
2918 assert setting.startswith('gitdir:')
2919 git_worktree_path = setting.split(':', 1)[1].strip()
2920 # Use relative path from checkout->worktree.
2921 with open(dotgit, 'w') as fp:
2922 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2923 file=fp)
2924 # Use relative path from worktree->checkout.
2925 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
2926 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2927
Martin Kellye4e94d22017-03-21 16:05:12 -07002928 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002929 realdotgit = os.path.join(self.worktree, '.git')
2930 tmpdotgit = realdotgit + '.tmp'
2931 init_dotgit = not os.path.exists(realdotgit)
2932 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002933 if self.use_git_worktrees:
2934 self._InitGitWorktree()
2935 self._CopyAndLinkFiles()
2936 return
2937
Mike Frysingerf4545122019-11-11 04:34:16 -05002938 dotgit = tmpdotgit
2939 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2940 os.makedirs(tmpdotgit)
2941 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2942 copy_all=False)
2943 else:
2944 dotgit = realdotgit
2945
Kevin Degib1a07b82015-07-27 13:33:43 -06002946 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002947 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2948 except GitError as e:
2949 if force_sync and not init_dotgit:
2950 try:
2951 platform_utils.rmtree(dotgit)
2952 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002953 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002954 raise e
2955 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002956
Mike Frysingerf4545122019-11-11 04:34:16 -05002957 if init_dotgit:
2958 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002959
Mike Frysingerf4545122019-11-11 04:34:16 -05002960 # Now that the .git dir is fully set up, move it to its final home.
2961 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962
Mike Frysingerf4545122019-11-11 04:34:16 -05002963 # Finish checking out the worktree.
2964 cmd = ['read-tree', '--reset', '-u']
2965 cmd.append('-v')
2966 cmd.append(HEAD)
2967 if GitCommand(self, cmd).Wait() != 0:
2968 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002969
Mike Frysingerf4545122019-11-11 04:34:16 -05002970 if submodules:
2971 self._SyncSubmodules(quiet=True)
2972 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002973
Renaud Paquay788e9622017-01-27 11:41:12 -08002974 def _get_symlink_error_message(self):
2975 if platform_utils.isWindows():
2976 return ('Unable to create symbolic link. Please re-run the command as '
2977 'Administrator, or see '
2978 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2979 'for other options.')
2980 return 'filesystem must support symlinks'
2981
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002982 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002983 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002985 def _revlist(self, *args, **kw):
2986 a = []
2987 a.extend(args)
2988 a.append('--')
2989 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002990
2991 @property
2992 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002993 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002994
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002995 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002996 """Get logs between two revisions of this project."""
2997 comp = '..'
2998 if rev1:
2999 revs = [rev1]
3000 if rev2:
3001 revs.extend([comp, rev2])
3002 cmd = ['log', ''.join(revs)]
3003 out = DiffColoring(self.config)
3004 if out.is_on and color:
3005 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003006 if pretty_format is not None:
3007 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003008 if oneline:
3009 cmd.append('--oneline')
3010
3011 try:
3012 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3013 if log.Wait() == 0:
3014 return log.stdout
3015 except GitError:
3016 # worktree may not exist if groups changed for example. In that case,
3017 # try in gitdir instead.
3018 if not os.path.exists(self.worktree):
3019 return self.bare_git.log(*cmd[1:])
3020 else:
3021 raise
3022 return None
3023
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003024 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3025 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003026 """Get the list of logs from this revision to given revisionId"""
3027 logs = {}
3028 selfId = self.GetRevisionId(self._allrefs)
3029 toId = toProject.GetRevisionId(toProject._allrefs)
3030
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003031 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3032 pretty_format=pretty_format)
3033 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3034 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003035 return logs
3036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003037 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003038
David James8d201162013-10-11 17:03:19 -07003039 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003040 self._project = project
3041 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003042 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003043
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003044 def LsOthers(self):
3045 p = GitCommand(self._project,
3046 ['ls-files',
3047 '-z',
3048 '--others',
3049 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003050 bare=False,
David James8d201162013-10-11 17:03:19 -07003051 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003052 capture_stdout=True,
3053 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003054 if p.Wait() == 0:
3055 out = p.stdout
3056 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003057 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003058 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003059 return []
3060
3061 def DiffZ(self, name, *args):
3062 cmd = [name]
3063 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003064 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003065 cmd.extend(args)
3066 p = GitCommand(self._project,
3067 cmd,
David James8d201162013-10-11 17:03:19 -07003068 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003069 bare=False,
3070 capture_stdout=True,
3071 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072 try:
3073 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003074 if not hasattr(out, 'encode'):
3075 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003076 r = {}
3077 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003078 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003079 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003080 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003081 info = next(out)
3082 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003083 except StopIteration:
3084 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003085
3086 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003087
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003088 def __init__(self, path, omode, nmode, oid, nid, state):
3089 self.path = path
3090 self.src_path = None
3091 self.old_mode = omode
3092 self.new_mode = nmode
3093 self.old_id = oid
3094 self.new_id = nid
3095
3096 if len(state) == 1:
3097 self.status = state
3098 self.level = None
3099 else:
3100 self.status = state[:1]
3101 self.level = state[1:]
3102 while self.level.startswith('0'):
3103 self.level = self.level[1:]
3104
3105 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003106 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003107 if info.status in ('R', 'C'):
3108 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003109 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003110 r[info.path] = info
3111 return r
3112 finally:
3113 p.Wait()
3114
3115 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003116 if self._bare:
3117 path = os.path.join(self._project.gitdir, HEAD)
3118 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003119 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003120 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003121 with open(path) as fd:
3122 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003123 except IOError as e:
3124 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003125 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303126 line = line.decode()
3127 except AttributeError:
3128 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003129 if line.startswith('ref: '):
3130 return line[5:-1]
3131 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003132
3133 def SetHead(self, ref, message=None):
3134 cmdv = []
3135 if message is not None:
3136 cmdv.extend(['-m', message])
3137 cmdv.append(HEAD)
3138 cmdv.append(ref)
3139 self.symbolic_ref(*cmdv)
3140
3141 def DetachHead(self, new, message=None):
3142 cmdv = ['--no-deref']
3143 if message is not None:
3144 cmdv.extend(['-m', message])
3145 cmdv.append(HEAD)
3146 cmdv.append(new)
3147 self.update_ref(*cmdv)
3148
3149 def UpdateRef(self, name, new, old=None,
3150 message=None,
3151 detach=False):
3152 cmdv = []
3153 if message is not None:
3154 cmdv.extend(['-m', message])
3155 if detach:
3156 cmdv.append('--no-deref')
3157 cmdv.append(name)
3158 cmdv.append(new)
3159 if old is not None:
3160 cmdv.append(old)
3161 self.update_ref(*cmdv)
3162
3163 def DeleteRef(self, name, old=None):
3164 if not old:
3165 old = self.rev_parse(name)
3166 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003167 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003169 def rev_list(self, *args, **kw):
3170 if 'format' in kw:
3171 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3172 else:
3173 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003174 cmdv.extend(args)
3175 p = GitCommand(self._project,
3176 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003177 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003178 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003179 capture_stdout=True,
3180 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003181 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003182 raise GitError('%s rev-list %s: %s' %
3183 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003184 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003185
3186 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003187 """Allow arbitrary git commands using pythonic syntax.
3188
3189 This allows you to do things like:
3190 git_obj.rev_parse('HEAD')
3191
3192 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3193 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003194 Any other positional arguments will be passed to the git command, and the
3195 following keyword arguments are supported:
3196 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003197
3198 Args:
3199 name: The name of the git command to call. Any '_' characters will
3200 be replaced with '-'.
3201
3202 Returns:
3203 A callable object that will try to call git with the named command.
3204 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003205 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003206
Dave Borowitz091f8932012-10-23 17:01:04 -07003207 def runner(*args, **kwargs):
3208 cmdv = []
3209 config = kwargs.pop('config', None)
3210 for k in kwargs:
3211 raise TypeError('%s() got an unexpected keyword argument %r'
3212 % (name, k))
3213 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303214 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003215 cmdv.append('-c')
3216 cmdv.append('%s=%s' % (k, v))
3217 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003218 cmdv.extend(args)
3219 p = GitCommand(self._project,
3220 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003221 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003222 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003223 capture_stdout=True,
3224 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003225 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003226 raise GitError('%s %s: %s' %
3227 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003228 r = p.stdout
3229 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3230 return r[:-1]
3231 return r
3232 return runner
3233
3234
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003236
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003237 def __str__(self):
3238 return 'prior sync failed; rebase still in progress'
3239
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003240
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003241class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003242
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003243 def __str__(self):
3244 return 'contains uncommitted changes'
3245
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003246
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003247class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003248
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003249 def __init__(self, project, text):
3250 self.project = project
3251 self.text = text
3252
3253 def Print(self, syncbuf):
3254 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3255 syncbuf.out.nl()
3256
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003257
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003258class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003259
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003260 def __init__(self, project, why):
3261 self.project = project
3262 self.why = why
3263
3264 def Print(self, syncbuf):
3265 syncbuf.out.fail('error: %s/: %s',
3266 self.project.relpath,
3267 str(self.why))
3268 syncbuf.out.nl()
3269
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003270
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003271class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003272
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003273 def __init__(self, project, action):
3274 self.project = project
3275 self.action = action
3276
3277 def Run(self, syncbuf):
3278 out = syncbuf.out
3279 out.project('project %s/', self.project.relpath)
3280 out.nl()
3281 try:
3282 self.action()
3283 out.nl()
3284 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003285 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003286 out.nl()
3287 return False
3288
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003289
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003290class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003291
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003292 def __init__(self, config):
3293 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003294 self.project = self.printer('header', attr='bold')
3295 self.info = self.printer('info')
3296 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003297
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003298
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003299class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003300
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003301 def __init__(self, config, detach_head=False):
3302 self._messages = []
3303 self._failures = []
3304 self._later_queue1 = []
3305 self._later_queue2 = []
3306
3307 self.out = _SyncColoring(config)
3308 self.out.redirect(sys.stderr)
3309
3310 self.detach_head = detach_head
3311 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003312 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003313
3314 def info(self, project, fmt, *args):
3315 self._messages.append(_InfoMessage(project, fmt % args))
3316
3317 def fail(self, project, err=None):
3318 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003319 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003320
3321 def later1(self, project, what):
3322 self._later_queue1.append(_Later(project, what))
3323
3324 def later2(self, project, what):
3325 self._later_queue2.append(_Later(project, what))
3326
3327 def Finish(self):
3328 self._PrintMessages()
3329 self._RunLater()
3330 self._PrintMessages()
3331 return self.clean
3332
David Rileye0684ad2017-04-05 00:02:59 -07003333 def Recently(self):
3334 recent_clean = self.recent_clean
3335 self.recent_clean = True
3336 return recent_clean
3337
3338 def _MarkUnclean(self):
3339 self.clean = False
3340 self.recent_clean = False
3341
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003342 def _RunLater(self):
3343 for q in ['_later_queue1', '_later_queue2']:
3344 if not self._RunQueue(q):
3345 return
3346
3347 def _RunQueue(self, queue):
3348 for m in getattr(self, queue):
3349 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003350 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003351 return False
3352 setattr(self, queue, [])
3353 return True
3354
3355 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003356 if self._messages or self._failures:
3357 if os.isatty(2):
3358 self.out.write(progress.CSI_ERASE_LINE)
3359 self.out.write('\r')
3360
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003361 for m in self._messages:
3362 m.Print(self)
3363 for m in self._failures:
3364 m.Print(self)
3365
3366 self._messages = []
3367 self._failures = []
3368
3369
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003370class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003371
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003372 """A special project housed under .repo.
3373 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003374
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003375 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003376 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003377 manifest=manifest,
3378 name=name,
3379 gitdir=gitdir,
3380 objdir=gitdir,
3381 worktree=worktree,
3382 remote=RemoteSpec('origin'),
3383 relpath='.repo/%s' % name,
3384 revisionExpr='refs/heads/master',
3385 revisionId=None,
3386 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003387
3388 def PreSync(self):
3389 if self.Exists:
3390 cb = self.CurrentBranch
3391 if cb:
3392 base = self.GetBranch(cb).merge
3393 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003394 self.revisionExpr = base
3395 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003396
Martin Kelly224a31a2017-07-10 14:46:25 -07003397 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003398 """ Prepare MetaProject for manifest branch switch
3399 """
3400
3401 # detach and delete manifest branch, allowing a new
3402 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003403 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003404 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003405 syncbuf.Finish()
3406
3407 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003408 ['update-ref', '-d', 'refs/heads/default'],
3409 capture_stdout=True,
3410 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003411
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003412 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003413 def LastFetch(self):
3414 try:
3415 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3416 return os.path.getmtime(fh)
3417 except OSError:
3418 return 0
3419
3420 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003421 def HasChanges(self):
3422 """Has the remote received new commits not yet checked out?
3423 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003424 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003425 return False
3426
David Pursehouse8a68ff92012-09-24 12:15:13 +09003427 all_refs = self.bare_ref.all
3428 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003429 head = self.work_git.GetHead()
3430 if head.startswith(R_HEADS):
3431 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003432 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003433 except KeyError:
3434 head = None
3435
3436 if revid == head:
3437 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003438 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003439 return True
3440 return False