blob: 3a7ac9e8e4134886f05050176d0f21f96ba2516b [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 ##
Zac Livingstone4332262017-06-16 08:56:09 -06002189 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002190 try:
2191 # if revision (sha or tag) is not present then following function
2192 # throws an error.
2193 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2194 return True
2195 except GitError:
2196 # There is no such persistent revision. We have to fetch it.
2197 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002198
Julien Campergue335f5ef2013-10-16 11:02:35 +02002199 def _FetchArchive(self, tarpath, cwd=None):
2200 cmd = ['archive', '-v', '-o', tarpath]
2201 cmd.append('--remote=%s' % self.remote.url)
2202 cmd.append('--prefix=%s/' % self.relpath)
2203 cmd.append(self.revisionExpr)
2204
2205 command = GitCommand(self, cmd, cwd=cwd,
2206 capture_stdout=True,
2207 capture_stderr=True)
2208
2209 if command.Wait() != 0:
2210 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2211
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002212 def _RemoteFetch(self, name=None,
2213 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002214 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002215 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002216 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002217 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002218 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002219 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002220 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002221 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002222 force_sync=False,
2223 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002224
2225 is_sha1 = False
2226 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002227 # The depth should not be used when fetching to a mirror because
2228 # it will result in a shallow repository that cannot be cloned or
2229 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002230 # The repo project should also never be synced with partial depth.
2231 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2232 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002233
Shawn Pearce69e04d82014-01-29 12:48:54 -08002234 if depth:
2235 current_branch_only = True
2236
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002237 if ID_RE.match(self.revisionExpr) is not None:
2238 is_sha1 = True
2239
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002240 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002241 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002242 # this is a tag and its sha1 value should never change
2243 tag_name = self.revisionExpr[len(R_TAGS):]
2244
2245 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002246 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002247 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002248 print('Skipped fetching project %s (already have persistent ref)'
2249 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002250 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002251 if is_sha1 and not depth:
2252 # When syncing a specific commit and --depth is not set:
2253 # * if upstream is explicitly specified and is not a sha1, fetch only
2254 # upstream as users expect only upstream to be fetch.
2255 # Note: The commit might not be in upstream in which case the sync
2256 # will fail.
2257 # * otherwise, fetch all branches to make sure we end up with the
2258 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002259 if self.upstream:
2260 current_branch_only = not ID_RE.match(self.upstream)
2261 else:
2262 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002263
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002264 if not name:
2265 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002266
2267 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002268 remote = self.GetRemote(name)
2269 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002270 ssh_proxy = True
2271
Shawn O. Pearce88443382010-10-08 10:02:09 +02002272 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002273 if alt_dir and 'objects' == os.path.basename(alt_dir):
2274 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002275 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2276 remote = self.GetRemote(name)
2277
David Pursehouse8a68ff92012-09-24 12:15:13 +09002278 all_refs = self.bare_ref.all
2279 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002280 tmp = set()
2281
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302282 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002283 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002284 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002285 all_refs[r] = ref_id
2286 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002287 continue
2288
David Pursehouse8a68ff92012-09-24 12:15:13 +09002289 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002290 continue
2291
David Pursehouse8a68ff92012-09-24 12:15:13 +09002292 r = 'refs/_alt/%s' % ref_id
2293 all_refs[r] = ref_id
2294 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002295 tmp.add(r)
2296
heping3d7bbc92017-04-12 19:51:47 +08002297 tmp_packed_lines = []
2298 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002299
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302300 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002301 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002302 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002303 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002304 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002305
heping3d7bbc92017-04-12 19:51:47 +08002306 tmp_packed = ''.join(tmp_packed_lines)
2307 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002308 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002309 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002310 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002311
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002312 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002313
Xin Li745be2e2019-06-03 11:24:30 -07002314 if clone_filter:
2315 git_require((2, 19, 0), fail=True, msg='partial clones')
2316 cmd.append('--filter=%s' % clone_filter)
2317 self.config.SetString('extensions.partialclone', self.remote.name)
2318
Conley Owensf97e8382015-01-21 11:12:46 -08002319 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002320 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002321 else:
2322 # If this repo has shallow objects, then we don't know which refs have
2323 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2324 # do this with projects that don't have shallow objects, since it is less
2325 # efficient.
2326 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2327 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002328
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002329 if quiet:
2330 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002331 if not self.worktree:
2332 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002333 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002334
Mike Frysingere57f1142019-03-18 21:27:54 -04002335 if force_sync:
2336 cmd.append('--force')
2337
David Pursehouse74cfd272015-10-14 10:50:15 +09002338 if prune:
2339 cmd.append('--prune')
2340
Martin Kellye4e94d22017-03-21 16:05:12 -07002341 if submodules:
2342 cmd.append('--recurse-submodules=on-demand')
2343
Kuang-che Wu6856f982019-11-25 12:37:55 +08002344 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002345 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002346 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002347 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002348 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002349 spec.append('tag')
2350 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002351
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302352 if self.manifest.IsMirror and not current_branch_only:
2353 branch = None
2354 else:
2355 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002356 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002357 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002358 # Shallow checkout of a specific commit, fetch from that commit and not
2359 # the heads only as the commit might be deeper in the history.
2360 spec.append(branch)
2361 else:
2362 if is_sha1:
2363 branch = self.upstream
2364 if branch is not None and branch.strip():
2365 if not branch.startswith('refs/'):
2366 branch = R_HEADS + branch
2367 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2368
2369 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2370 # whole repo.
2371 if self.manifest.IsMirror and not spec:
2372 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2373
2374 # If using depth then we should not get all the tags since they may
2375 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002376 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002377 cmd.append('--no-tags')
2378 else:
2379 cmd.append('--tags')
2380 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2381
Conley Owens80b87fe2014-05-09 17:13:44 -07002382 cmd.extend(spec)
2383
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002384 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002385 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002386 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2387 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002388 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002389 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002390 ok = True
2391 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002392 # If needed, run the 'git remote prune' the first time through the loop
2393 elif (not _i and
2394 "error:" in gitcmd.stderr and
2395 "git remote prune" in gitcmd.stderr):
2396 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002397 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002398 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002399 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002400 break
2401 continue
Brian Harring14a66742012-09-28 20:21:57 -07002402 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002403 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2404 # in sha1 mode, we just tried sync'ing from the upstream field; it
2405 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002406 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002407 elif ret < 0:
2408 # Git died with a signal, exit immediately
2409 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002410 if not verbose:
2411 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002412 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002413
2414 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002415 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002416 if old_packed != '':
2417 _lwrite(packed_refs, old_packed)
2418 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002419 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002420 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002421
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002422 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002423 # We just synced the upstream given branch; verify we
2424 # got what we wanted, else trigger a second run of all
2425 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002426 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002427 # Sync the current branch only with depth set to None.
2428 # We always pass depth=None down to avoid infinite recursion.
2429 return self._RemoteFetch(
2430 name=name, quiet=quiet, verbose=verbose,
2431 current_branch_only=current_branch_only and depth,
2432 initial=False, alt_dir=alt_dir,
2433 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002434
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002435 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002436
Mike Frysingere50b6a72020-02-19 01:45:48 -05002437 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002438 if initial and \
2439 (self.manifest.manifestProject.config.GetString('repo.depth') or
2440 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002441 return False
2442
2443 remote = self.GetRemote(self.remote.name)
2444 bundle_url = remote.url + '/clone.bundle'
2445 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002446 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2447 'persistent-http',
2448 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002449 return False
2450
2451 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2452 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2453
2454 exist_dst = os.path.exists(bundle_dst)
2455 exist_tmp = os.path.exists(bundle_tmp)
2456
2457 if not initial and not exist_dst and not exist_tmp:
2458 return False
2459
2460 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002461 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2462 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002463 if not exist_dst:
2464 return False
2465
2466 cmd = ['fetch']
2467 if quiet:
2468 cmd.append('--quiet')
2469 if not self.worktree:
2470 cmd.append('--update-head-ok')
2471 cmd.append(bundle_dst)
2472 for f in remote.fetch:
2473 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002474 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002475
2476 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002477 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002478 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002479 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002480 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002481 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002482
Mike Frysingere50b6a72020-02-19 01:45:48 -05002483 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002484 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002485 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002486
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002487 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002488 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002489 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002490 if os.path.exists(tmpPath):
2491 size = os.stat(tmpPath).st_size
2492 if size >= 1024:
2493 cmd += ['--continue-at', '%d' % (size,)]
2494 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002495 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002496 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002497 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002498 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002499 if proxy:
2500 cmd += ['--proxy', proxy]
2501 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2502 cmd += ['--proxy', os.environ['http_proxy']]
2503 if srcUrl.startswith('persistent-https'):
2504 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2505 elif srcUrl.startswith('persistent-http'):
2506 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002507 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002508
Dave Borowitz137d0132015-01-02 11:12:54 -08002509 if IsTrace():
2510 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002511 if verbose:
2512 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2513 stdout = None if verbose else subprocess.PIPE
2514 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002515 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002516 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002517 except OSError:
2518 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002519
Mike Frysingere50b6a72020-02-19 01:45:48 -05002520 (output, _) = proc.communicate()
2521 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002522
Dave Borowitz137d0132015-01-02 11:12:54 -08002523 if curlret == 22:
2524 # From curl man page:
2525 # 22: HTTP page not retrieved. The requested url was not found or
2526 # returned another error with the HTTP error code being 400 or above.
2527 # This return code only appears if -f, --fail is used.
2528 if not quiet:
2529 print("Server does not provide clone.bundle; ignoring.",
2530 file=sys.stderr)
2531 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002532 elif curlret and not verbose and output:
2533 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002534
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002535 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002536 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002537 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002538 return True
2539 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002540 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002541 return False
2542 else:
2543 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002544
Kris Giesingc8d882a2014-12-23 13:02:32 -08002545 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002546 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002547 with open(path, 'rb') as f:
2548 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002549 return True
2550 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002551 if not quiet:
2552 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002553 return False
2554 except OSError:
2555 return False
2556
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002557 def _Checkout(self, rev, quiet=False):
2558 cmd = ['checkout']
2559 if quiet:
2560 cmd.append('-q')
2561 cmd.append(rev)
2562 cmd.append('--')
2563 if GitCommand(self, cmd).Wait() != 0:
2564 if self._allrefs:
2565 raise GitError('%s checkout %s ' % (self.name, rev))
2566
Anthony King7bdac712014-07-16 12:56:40 +01002567 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002568 cmd = ['cherry-pick']
2569 cmd.append(rev)
2570 cmd.append('--')
2571 if GitCommand(self, cmd).Wait() != 0:
2572 if self._allrefs:
2573 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2574
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302575 def _LsRemote(self, refs):
2576 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302577 p = GitCommand(self, cmd, capture_stdout=True)
2578 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002579 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302580 return None
2581
Anthony King7bdac712014-07-16 12:56:40 +01002582 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002583 cmd = ['revert']
2584 cmd.append('--no-edit')
2585 cmd.append(rev)
2586 cmd.append('--')
2587 if GitCommand(self, cmd).Wait() != 0:
2588 if self._allrefs:
2589 raise GitError('%s revert %s ' % (self.name, rev))
2590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002591 def _ResetHard(self, rev, quiet=True):
2592 cmd = ['reset', '--hard']
2593 if quiet:
2594 cmd.append('-q')
2595 cmd.append(rev)
2596 if GitCommand(self, cmd).Wait() != 0:
2597 raise GitError('%s reset --hard %s ' % (self.name, rev))
2598
Martin Kellye4e94d22017-03-21 16:05:12 -07002599 def _SyncSubmodules(self, quiet=True):
2600 cmd = ['submodule', 'update', '--init', '--recursive']
2601 if quiet:
2602 cmd.append('-q')
2603 if GitCommand(self, cmd).Wait() != 0:
2604 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2605
Anthony King7bdac712014-07-16 12:56:40 +01002606 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002607 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 if onto is not None:
2609 cmd.extend(['--onto', onto])
2610 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002611 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 raise GitError('%s rebase %s ' % (self.name, upstream))
2613
Pierre Tardy3d125942012-05-04 12:18:12 +02002614 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002615 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002616 if ffonly:
2617 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002618 if GitCommand(self, cmd).Wait() != 0:
2619 raise GitError('%s merge %s ' % (self.name, head))
2620
David Pursehousee8ace262020-02-13 12:41:15 +09002621 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002622 init_git_dir = not os.path.exists(self.gitdir)
2623 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002624 try:
2625 # Initialize the bare repository, which contains all of the objects.
2626 if init_obj_dir:
2627 os.makedirs(self.objdir)
2628 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002629
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002630 # Enable per-worktree config file support if possible. This is more a
2631 # nice-to-have feature for users rather than a hard requirement.
2632 if self.use_git_worktrees and git_require((2, 19, 0)):
2633 self.config.SetString('extensions.worktreeConfig', 'true')
2634
Kevin Degib1a07b82015-07-27 13:33:43 -06002635 # If we have a separate directory to hold refs, initialize it as well.
2636 if self.objdir != self.gitdir:
2637 if init_git_dir:
2638 os.makedirs(self.gitdir)
2639
2640 if init_obj_dir or init_git_dir:
2641 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2642 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002643 try:
2644 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2645 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002646 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002647 print("Retrying clone after deleting %s" %
2648 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002649 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002650 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2651 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002652 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002653 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002654 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2655 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002656 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002657 raise e
2658 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002659
Kevin Degi384b3c52014-10-16 16:02:58 -06002660 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002661 mp = self.manifest.manifestProject
2662 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002663
Kevin Degib1a07b82015-07-27 13:33:43 -06002664 if ref_dir or mirror_git:
2665 if not mirror_git:
2666 mirror_git = os.path.join(ref_dir, self.name + '.git')
2667 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2668 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002669 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2670 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002671
Kevin Degib1a07b82015-07-27 13:33:43 -06002672 if os.path.exists(mirror_git):
2673 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002674 elif os.path.exists(repo_git):
2675 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002676 elif os.path.exists(worktrees_git):
2677 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002678 else:
2679 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002680
Kevin Degib1a07b82015-07-27 13:33:43 -06002681 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002682 if not os.path.isabs(ref_dir):
2683 # The alternate directory is relative to the object database.
2684 ref_dir = os.path.relpath(ref_dir,
2685 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002686 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2687 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002688
David Pursehousee8ace262020-02-13 12:41:15 +09002689 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002690
2691 m = self.manifest.manifestProject.config
2692 for key in ['user.name', 'user.email']:
2693 if m.Has(key, include_defaults=False):
2694 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002695 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002696 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002697 if self.manifest.IsMirror:
2698 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002699 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002700 self.config.SetString('core.bare', None)
2701 except Exception:
2702 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002703 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002704 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002705 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002706 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002707
David Pursehousee8ace262020-02-13 12:41:15 +09002708 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002709 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002710 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002711
David Pursehousee8ace262020-02-13 12:41:15 +09002712 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002713 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002714 if not os.path.exists(hooks):
2715 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002716 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002717 name = os.path.basename(stock_hook)
2718
Victor Boivie65e0f352011-04-18 11:23:29 +02002719 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002720 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002721 # Don't install a Gerrit Code Review hook if this
2722 # project does not appear to use it for reviews.
2723 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002724 # Since the manifest project is one of those, but also
2725 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002726 continue
2727
2728 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002729 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002730 continue
2731 if os.path.exists(dst):
2732 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002733 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002734 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002735 if not quiet:
2736 _warn("%s: Not replacing locally modified %s hook",
2737 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002738 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002739 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002740 platform_utils.symlink(
2741 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002742 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002743 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002744 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002745 else:
2746 raise
2747
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002749 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002751 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002752 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002753 remote.review = self.remote.review
2754 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002755
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002756 if self.worktree:
2757 remote.ResetFetch(mirror=False)
2758 else:
2759 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002760 remote.Save()
2761
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002762 def _InitMRef(self):
2763 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002764 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002766 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002767 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002768
2769 def _InitAnyMRef(self, ref):
2770 cur = self.bare_ref.symref(ref)
2771
2772 if self.revisionId:
2773 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2774 msg = 'manifest set to %s' % self.revisionId
2775 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002776 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002777 else:
2778 remote = self.GetRemote(self.remote.name)
2779 dst = remote.ToLocal(self.revisionExpr)
2780 if cur != dst:
2781 msg = 'manifest set to %s' % self.revisionExpr
2782 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002783
Kevin Degi384b3c52014-10-16 16:02:58 -06002784 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002785 # Git worktrees don't use symlinks to share at all.
2786 if self.use_git_worktrees:
2787 return
2788
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002789 symlink_files = self.shareable_files[:]
2790 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002791 if share_refs:
2792 symlink_files += self.working_tree_files
2793 symlink_dirs += self.working_tree_dirs
2794 to_symlink = symlink_files + symlink_dirs
2795 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002796 # Try to self-heal a bit in simple cases.
2797 dst_path = os.path.join(destdir, name)
2798 src_path = os.path.join(srcdir, name)
2799
2800 if name in self.working_tree_dirs:
2801 # If the dir is missing under .repo/projects/, create it.
2802 if not os.path.exists(src_path):
2803 os.makedirs(src_path)
2804
2805 elif name in self.working_tree_files:
2806 # If it's a file under the checkout .git/ and the .repo/projects/ has
2807 # nothing, move the file under the .repo/projects/ tree.
2808 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2809 platform_utils.rename(dst_path, src_path)
2810
2811 # If the path exists under the .repo/projects/ and there's no symlink
2812 # under the checkout .git/, recreate the symlink.
2813 if name in self.working_tree_dirs or name in self.working_tree_files:
2814 if os.path.exists(src_path) and not os.path.exists(dst_path):
2815 platform_utils.symlink(
2816 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2817
2818 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002819 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002820 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002821 # Fail if the links are pointing to the wrong place
2822 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002823 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002824 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002825 'work tree. If you\'re comfortable with the '
2826 'possibility of losing the work tree\'s git metadata,'
2827 ' use `repo sync --force-sync {0}` to '
2828 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002829
David James8d201162013-10-11 17:03:19 -07002830 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2831 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2832
2833 Args:
2834 gitdir: The bare git repository. Must already be initialized.
2835 dotgit: The repository you would like to initialize.
2836 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2837 Only one work tree can store refs under a given |gitdir|.
2838 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2839 This saves you the effort of initializing |dotgit| yourself.
2840 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002841 symlink_files = self.shareable_files[:]
2842 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002843 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002844 symlink_files += self.working_tree_files
2845 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002846 to_symlink = symlink_files + symlink_dirs
2847
2848 to_copy = []
2849 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002850 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002851
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002852 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002853 for name in set(to_copy).union(to_symlink):
2854 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002855 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002856 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002857
Kevin Degi384b3c52014-10-16 16:02:58 -06002858 if os.path.lexists(dst):
2859 continue
David James8d201162013-10-11 17:03:19 -07002860
2861 # If the source dir doesn't exist, create an empty dir.
2862 if name in symlink_dirs and not os.path.lexists(src):
2863 os.makedirs(src)
2864
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002865 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002866 platform_utils.symlink(
2867 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002868 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002869 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002870 shutil.copytree(src, dst)
2871 elif os.path.isfile(src):
2872 shutil.copy(src, dst)
2873
Conley Owens80b87fe2014-05-09 17:13:44 -07002874 # If the source file doesn't exist, ensure the destination
2875 # file doesn't either.
2876 if name in symlink_files and not os.path.lexists(src):
2877 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002878 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002879 except OSError:
2880 pass
2881
David James8d201162013-10-11 17:03:19 -07002882 except OSError as e:
2883 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002884 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002885 else:
2886 raise
2887
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002888 def _InitGitWorktree(self):
2889 """Init the project using git worktrees."""
2890 self.bare_git.worktree('prune')
2891 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2892 self.worktree, self.GetRevisionId())
2893
2894 # Rewrite the internal state files to use relative paths between the
2895 # checkouts & worktrees.
2896 dotgit = os.path.join(self.worktree, '.git')
2897 with open(dotgit, 'r') as fp:
2898 # Figure out the checkout->worktree path.
2899 setting = fp.read()
2900 assert setting.startswith('gitdir:')
2901 git_worktree_path = setting.split(':', 1)[1].strip()
2902 # Use relative path from checkout->worktree.
2903 with open(dotgit, 'w') as fp:
2904 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2905 file=fp)
2906 # Use relative path from worktree->checkout.
2907 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
2908 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2909
Martin Kellye4e94d22017-03-21 16:05:12 -07002910 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002911 realdotgit = os.path.join(self.worktree, '.git')
2912 tmpdotgit = realdotgit + '.tmp'
2913 init_dotgit = not os.path.exists(realdotgit)
2914 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002915 if self.use_git_worktrees:
2916 self._InitGitWorktree()
2917 self._CopyAndLinkFiles()
2918 return
2919
Mike Frysingerf4545122019-11-11 04:34:16 -05002920 dotgit = tmpdotgit
2921 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2922 os.makedirs(tmpdotgit)
2923 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2924 copy_all=False)
2925 else:
2926 dotgit = realdotgit
2927
Kevin Degib1a07b82015-07-27 13:33:43 -06002928 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002929 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2930 except GitError as e:
2931 if force_sync and not init_dotgit:
2932 try:
2933 platform_utils.rmtree(dotgit)
2934 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002935 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002936 raise e
2937 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002938
Mike Frysingerf4545122019-11-11 04:34:16 -05002939 if init_dotgit:
2940 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002941
Mike Frysingerf4545122019-11-11 04:34:16 -05002942 # Now that the .git dir is fully set up, move it to its final home.
2943 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002944
Mike Frysingerf4545122019-11-11 04:34:16 -05002945 # Finish checking out the worktree.
2946 cmd = ['read-tree', '--reset', '-u']
2947 cmd.append('-v')
2948 cmd.append(HEAD)
2949 if GitCommand(self, cmd).Wait() != 0:
2950 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002951
Mike Frysingerf4545122019-11-11 04:34:16 -05002952 if submodules:
2953 self._SyncSubmodules(quiet=True)
2954 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002955
Renaud Paquay788e9622017-01-27 11:41:12 -08002956 def _get_symlink_error_message(self):
2957 if platform_utils.isWindows():
2958 return ('Unable to create symbolic link. Please re-run the command as '
2959 'Administrator, or see '
2960 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2961 'for other options.')
2962 return 'filesystem must support symlinks'
2963
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002964 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002965 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002967 def _revlist(self, *args, **kw):
2968 a = []
2969 a.extend(args)
2970 a.append('--')
2971 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002972
2973 @property
2974 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002975 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002976
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002977 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002978 """Get logs between two revisions of this project."""
2979 comp = '..'
2980 if rev1:
2981 revs = [rev1]
2982 if rev2:
2983 revs.extend([comp, rev2])
2984 cmd = ['log', ''.join(revs)]
2985 out = DiffColoring(self.config)
2986 if out.is_on and color:
2987 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002988 if pretty_format is not None:
2989 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002990 if oneline:
2991 cmd.append('--oneline')
2992
2993 try:
2994 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2995 if log.Wait() == 0:
2996 return log.stdout
2997 except GitError:
2998 # worktree may not exist if groups changed for example. In that case,
2999 # try in gitdir instead.
3000 if not os.path.exists(self.worktree):
3001 return self.bare_git.log(*cmd[1:])
3002 else:
3003 raise
3004 return None
3005
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003006 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3007 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003008 """Get the list of logs from this revision to given revisionId"""
3009 logs = {}
3010 selfId = self.GetRevisionId(self._allrefs)
3011 toId = toProject.GetRevisionId(toProject._allrefs)
3012
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003013 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3014 pretty_format=pretty_format)
3015 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3016 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003017 return logs
3018
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003019 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003020
David James8d201162013-10-11 17:03:19 -07003021 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003022 self._project = project
3023 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003024 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003025
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003026 def LsOthers(self):
3027 p = GitCommand(self._project,
3028 ['ls-files',
3029 '-z',
3030 '--others',
3031 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003032 bare=False,
David James8d201162013-10-11 17:03:19 -07003033 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003034 capture_stdout=True,
3035 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003036 if p.Wait() == 0:
3037 out = p.stdout
3038 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003039 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003040 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003041 return []
3042
3043 def DiffZ(self, name, *args):
3044 cmd = [name]
3045 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003046 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003047 cmd.extend(args)
3048 p = GitCommand(self._project,
3049 cmd,
David James8d201162013-10-11 17:03:19 -07003050 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003051 bare=False,
3052 capture_stdout=True,
3053 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003054 try:
3055 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003056 if not hasattr(out, 'encode'):
3057 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003058 r = {}
3059 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003060 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003061 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003062 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003063 info = next(out)
3064 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003065 except StopIteration:
3066 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003067
3068 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003069
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003070 def __init__(self, path, omode, nmode, oid, nid, state):
3071 self.path = path
3072 self.src_path = None
3073 self.old_mode = omode
3074 self.new_mode = nmode
3075 self.old_id = oid
3076 self.new_id = nid
3077
3078 if len(state) == 1:
3079 self.status = state
3080 self.level = None
3081 else:
3082 self.status = state[:1]
3083 self.level = state[1:]
3084 while self.level.startswith('0'):
3085 self.level = self.level[1:]
3086
3087 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003088 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003089 if info.status in ('R', 'C'):
3090 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003091 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003092 r[info.path] = info
3093 return r
3094 finally:
3095 p.Wait()
3096
3097 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003098 if self._bare:
3099 path = os.path.join(self._project.gitdir, HEAD)
3100 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003101 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003102 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003103 with open(path) as fd:
3104 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003105 except IOError as e:
3106 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003107 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303108 line = line.decode()
3109 except AttributeError:
3110 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003111 if line.startswith('ref: '):
3112 return line[5:-1]
3113 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003114
3115 def SetHead(self, ref, message=None):
3116 cmdv = []
3117 if message is not None:
3118 cmdv.extend(['-m', message])
3119 cmdv.append(HEAD)
3120 cmdv.append(ref)
3121 self.symbolic_ref(*cmdv)
3122
3123 def DetachHead(self, new, message=None):
3124 cmdv = ['--no-deref']
3125 if message is not None:
3126 cmdv.extend(['-m', message])
3127 cmdv.append(HEAD)
3128 cmdv.append(new)
3129 self.update_ref(*cmdv)
3130
3131 def UpdateRef(self, name, new, old=None,
3132 message=None,
3133 detach=False):
3134 cmdv = []
3135 if message is not None:
3136 cmdv.extend(['-m', message])
3137 if detach:
3138 cmdv.append('--no-deref')
3139 cmdv.append(name)
3140 cmdv.append(new)
3141 if old is not None:
3142 cmdv.append(old)
3143 self.update_ref(*cmdv)
3144
3145 def DeleteRef(self, name, old=None):
3146 if not old:
3147 old = self.rev_parse(name)
3148 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003149 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003150
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003151 def rev_list(self, *args, **kw):
3152 if 'format' in kw:
3153 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3154 else:
3155 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003156 cmdv.extend(args)
3157 p = GitCommand(self._project,
3158 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003159 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003160 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003161 capture_stdout=True,
3162 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003163 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003164 raise GitError('%s rev-list %s: %s' %
3165 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003166 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003167
3168 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003169 """Allow arbitrary git commands using pythonic syntax.
3170
3171 This allows you to do things like:
3172 git_obj.rev_parse('HEAD')
3173
3174 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3175 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003176 Any other positional arguments will be passed to the git command, and the
3177 following keyword arguments are supported:
3178 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003179
3180 Args:
3181 name: The name of the git command to call. Any '_' characters will
3182 be replaced with '-'.
3183
3184 Returns:
3185 A callable object that will try to call git with the named command.
3186 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003187 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
Dave Borowitz091f8932012-10-23 17:01:04 -07003189 def runner(*args, **kwargs):
3190 cmdv = []
3191 config = kwargs.pop('config', None)
3192 for k in kwargs:
3193 raise TypeError('%s() got an unexpected keyword argument %r'
3194 % (name, k))
3195 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303196 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003197 cmdv.append('-c')
3198 cmdv.append('%s=%s' % (k, v))
3199 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003200 cmdv.extend(args)
3201 p = GitCommand(self._project,
3202 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003203 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003204 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003205 capture_stdout=True,
3206 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003207 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003208 raise GitError('%s %s: %s' %
3209 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003210 r = p.stdout
3211 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3212 return r[:-1]
3213 return r
3214 return runner
3215
3216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003219 def __str__(self):
3220 return 'prior sync failed; rebase still in progress'
3221
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003222
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003223class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003224
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003225 def __str__(self):
3226 return 'contains uncommitted changes'
3227
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003228
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003230
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003231 def __init__(self, project, text):
3232 self.project = project
3233 self.text = text
3234
3235 def Print(self, syncbuf):
3236 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3237 syncbuf.out.nl()
3238
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003239
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003240class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003241
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003242 def __init__(self, project, why):
3243 self.project = project
3244 self.why = why
3245
3246 def Print(self, syncbuf):
3247 syncbuf.out.fail('error: %s/: %s',
3248 self.project.relpath,
3249 str(self.why))
3250 syncbuf.out.nl()
3251
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003252
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003253class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003254
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003255 def __init__(self, project, action):
3256 self.project = project
3257 self.action = action
3258
3259 def Run(self, syncbuf):
3260 out = syncbuf.out
3261 out.project('project %s/', self.project.relpath)
3262 out.nl()
3263 try:
3264 self.action()
3265 out.nl()
3266 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003267 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003268 out.nl()
3269 return False
3270
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003271
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003272class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003273
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003274 def __init__(self, config):
3275 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003276 self.project = self.printer('header', attr='bold')
3277 self.info = self.printer('info')
3278 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003279
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003280
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003281class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003282
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003283 def __init__(self, config, detach_head=False):
3284 self._messages = []
3285 self._failures = []
3286 self._later_queue1 = []
3287 self._later_queue2 = []
3288
3289 self.out = _SyncColoring(config)
3290 self.out.redirect(sys.stderr)
3291
3292 self.detach_head = detach_head
3293 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003294 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003295
3296 def info(self, project, fmt, *args):
3297 self._messages.append(_InfoMessage(project, fmt % args))
3298
3299 def fail(self, project, err=None):
3300 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003301 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003302
3303 def later1(self, project, what):
3304 self._later_queue1.append(_Later(project, what))
3305
3306 def later2(self, project, what):
3307 self._later_queue2.append(_Later(project, what))
3308
3309 def Finish(self):
3310 self._PrintMessages()
3311 self._RunLater()
3312 self._PrintMessages()
3313 return self.clean
3314
David Rileye0684ad2017-04-05 00:02:59 -07003315 def Recently(self):
3316 recent_clean = self.recent_clean
3317 self.recent_clean = True
3318 return recent_clean
3319
3320 def _MarkUnclean(self):
3321 self.clean = False
3322 self.recent_clean = False
3323
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003324 def _RunLater(self):
3325 for q in ['_later_queue1', '_later_queue2']:
3326 if not self._RunQueue(q):
3327 return
3328
3329 def _RunQueue(self, queue):
3330 for m in getattr(self, queue):
3331 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003332 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003333 return False
3334 setattr(self, queue, [])
3335 return True
3336
3337 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003338 if self._messages or self._failures:
3339 if os.isatty(2):
3340 self.out.write(progress.CSI_ERASE_LINE)
3341 self.out.write('\r')
3342
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003343 for m in self._messages:
3344 m.Print(self)
3345 for m in self._failures:
3346 m.Print(self)
3347
3348 self._messages = []
3349 self._failures = []
3350
3351
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003352class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003354 """A special project housed under .repo.
3355 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003356
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003357 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003358 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003359 manifest=manifest,
3360 name=name,
3361 gitdir=gitdir,
3362 objdir=gitdir,
3363 worktree=worktree,
3364 remote=RemoteSpec('origin'),
3365 relpath='.repo/%s' % name,
3366 revisionExpr='refs/heads/master',
3367 revisionId=None,
3368 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003369
3370 def PreSync(self):
3371 if self.Exists:
3372 cb = self.CurrentBranch
3373 if cb:
3374 base = self.GetBranch(cb).merge
3375 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003376 self.revisionExpr = base
3377 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003378
Martin Kelly224a31a2017-07-10 14:46:25 -07003379 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003380 """ Prepare MetaProject for manifest branch switch
3381 """
3382
3383 # detach and delete manifest branch, allowing a new
3384 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003385 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003386 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003387 syncbuf.Finish()
3388
3389 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003390 ['update-ref', '-d', 'refs/heads/default'],
3391 capture_stdout=True,
3392 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003393
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003394 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003395 def LastFetch(self):
3396 try:
3397 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3398 return os.path.getmtime(fh)
3399 except OSError:
3400 return 0
3401
3402 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003403 def HasChanges(self):
3404 """Has the remote received new commits not yet checked out?
3405 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003406 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003407 return False
3408
David Pursehouse8a68ff92012-09-24 12:15:13 +09003409 all_refs = self.bare_ref.all
3410 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003411 head = self.work_git.GetHead()
3412 if head.startswith(R_HEADS):
3413 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003414 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003415 except KeyError:
3416 head = None
3417
3418 if revid == head:
3419 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003420 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003421 return True
3422 return False