blob: 2112ee32eb45ea53374fa45ab8e61f7782519efe [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000204 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000216 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
242 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
256 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Mike Frysingere6a202f2019-08-02 15:57:57 -0400269def _SafeExpandPath(base, subpath, skipfinal=False):
270 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700271
Mike Frysingere6a202f2019-08-02 15:57:57 -0400272 We make sure no intermediate symlinks are traversed, and that the final path
273 is not a special file (e.g. not a socket or fifo).
274
275 NB: We rely on a number of paths already being filtered out while parsing the
276 manifest. See the validation logic in manifest_xml.py for more details.
277 """
278 components = subpath.split(os.path.sep)
279 if skipfinal:
280 # Whether the caller handles the final component itself.
281 finalpart = components.pop()
282
283 path = base
284 for part in components:
285 if part in {'.', '..'}:
286 raise ManifestInvalidPathError(
287 '%s: "%s" not allowed in paths' % (subpath, part))
288
289 path = os.path.join(path, part)
290 if platform_utils.islink(path):
291 raise ManifestInvalidPathError(
292 '%s: traversing symlinks not allow' % (path,))
293
294 if os.path.exists(path):
295 if not os.path.isfile(path) and not platform_utils.isdir(path):
296 raise ManifestInvalidPathError(
297 '%s: only regular files & directories allowed' % (path,))
298
299 if skipfinal:
300 path = os.path.join(path, finalpart)
301
302 return path
303
304
305class _CopyFile(object):
306 """Container for <copyfile> manifest element."""
307
308 def __init__(self, git_worktree, src, topdir, dest):
309 """Register a <copyfile> request.
310
311 Args:
312 git_worktree: Absolute path to the git project checkout.
313 src: Relative path under |git_worktree| of file to read.
314 topdir: Absolute path to the top of the repo client checkout.
315 dest: Relative path under |topdir| of file to write.
316 """
317 self.git_worktree = git_worktree
318 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700319 self.src = src
320 self.dest = dest
321
322 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400323 src = _SafeExpandPath(self.git_worktree, self.src)
324 dest = _SafeExpandPath(self.topdir, self.dest)
325
326 if platform_utils.isdir(src):
327 raise ManifestInvalidPathError(
328 '%s: copying from directory not supported' % (self.src,))
329 if platform_utils.isdir(dest):
330 raise ManifestInvalidPathError(
331 '%s: copying to directory not allowed' % (self.dest,))
332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333 # copy file if it does not exist or is out of date
334 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
335 try:
336 # remove existing file first, since it might be read-only
337 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800338 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400339 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700341 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200342 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 shutil.copy(src, dest)
344 # make the file read-only
345 mode = os.stat(dest)[stat.ST_MODE]
346 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
347 os.chmod(dest, mode)
348 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700349 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700351
Anthony King7bdac712014-07-16 12:56:40 +0100352class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700354
Mike Frysingere6a202f2019-08-02 15:57:57 -0400355 def __init__(self, git_worktree, src, topdir, dest):
356 """Register a <linkfile> request.
357
358 Args:
359 git_worktree: Absolute path to the git project checkout.
360 src: Target of symlink relative to path under |git_worktree|.
361 topdir: Absolute path to the top of the repo client checkout.
362 dest: Relative path under |topdir| of symlink to create.
363 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700364 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400365 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366 self.src = src
367 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368
Wink Saville4c426ef2015-06-03 08:05:17 -0700369 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700371 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500372 try:
373 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800374 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800375 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500376 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700377 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700378 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700380 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700382 _error('Cannot link file %s to %s', relSrc, absDest)
383
384 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400385 """Link the self.src & self.dest paths.
386
387 Handles wild cards on the src linking all of the files in the source in to
388 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700389 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500390 # Some people use src="." to create stable links to projects. Lets allow
391 # that but reject all other uses of "." to keep things simple.
392 if self.src == '.':
393 src = self.git_worktree
394 else:
395 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400396
397 if os.path.exists(src):
398 # Entity exists so just a simple one to one link operation.
399 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
400 # dest & src are absolute paths at this point. Make sure the target of
401 # the symlink is relative in the context of the repo client checkout.
402 relpath = os.path.relpath(src, os.path.dirname(dest))
403 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700406 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400407 if os.path.exists(dest) and not platform_utils.isdir(dest):
408 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 # Create a releative path from source dir to destination dir
412 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400413 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700414
415 # Get the source file name
416 srcFile = os.path.basename(absSrcFile)
417
418 # Now form the final full paths to srcFile. They will be
419 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700421 relSrc = os.path.join(relSrcDir, srcFile)
422 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500423
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700426
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700427 def __init__(self,
428 name,
Anthony King7bdac712014-07-16 12:56:40 +0100429 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700430 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100431 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700432 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700433 orig_name=None,
434 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700435 self.name = name
436 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700437 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700438 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100439 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700440 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700441 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700443
Doug Anderson37282b42011-03-04 11:54:18 -0800444class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700445
Doug Anderson37282b42011-03-04 11:54:18 -0800446 """A RepoHook contains information about a script to run as a hook.
447
448 Hooks are used to run a python script before running an upload (for instance,
449 to run presubmit checks). Eventually, we may have hooks for other actions.
450
451 This shouldn't be confused with files in the 'repo/hooks' directory. Those
452 files are copied into each '.git/hooks' folder for each project. Repo-level
453 hooks are associated instead with repo actions.
454
455 Hooks are always python. When a hook is run, we will load the hook into the
456 interpreter and execute its main() function.
457 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700458
Doug Anderson37282b42011-03-04 11:54:18 -0800459 def __init__(self,
460 hook_type,
461 hooks_project,
462 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400463 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800464 abort_if_user_denies=False):
465 """RepoHook constructor.
466
467 Params:
468 hook_type: A string representing the type of hook. This is also used
469 to figure out the name of the file containing the hook. For
470 example: 'pre-upload'.
471 hooks_project: The project containing the repo hooks. If you have a
472 manifest, this is manifest.repo_hooks_project. OK if this is None,
473 which will make the hook a no-op.
474 topdir: Repo's top directory (the one containing the .repo directory).
475 Scripts will run with CWD as this directory. If you have a manifest,
476 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800478 abort_if_user_denies: If True, we'll throw a HookError() if the user
479 doesn't allow us to run the hook.
480 """
481 self._hook_type = hook_type
482 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800484 self._topdir = topdir
485 self._abort_if_user_denies = abort_if_user_denies
486
487 # Store the full path to the script for convenience.
488 if self._hooks_project:
489 self._script_fullpath = os.path.join(self._hooks_project.worktree,
490 self._hook_type + '.py')
491 else:
492 self._script_fullpath = None
493
494 def _GetHash(self):
495 """Return a hash of the contents of the hooks directory.
496
497 We'll just use git to do this. This hash has the property that if anything
498 changes in the directory we will return a different has.
499
500 SECURITY CONSIDERATION:
501 This hash only represents the contents of files in the hook directory, not
502 any other files imported or called by hooks. Changes to imported files
503 can change the script behavior without affecting the hash.
504
505 Returns:
506 A string representing the hash. This will always be ASCII so that it can
507 be printed to the user easily.
508 """
509 assert self._hooks_project, "Must have hooks to calculate their hash."
510
511 # We will use the work_git object rather than just calling GetRevisionId().
512 # That gives us a hash of the latest checked in version of the files that
513 # the user will actually be executing. Specifically, GetRevisionId()
514 # doesn't appear to change even if a user checks out a different version
515 # of the hooks repo (via git checkout) nor if a user commits their own revs.
516 #
517 # NOTE: Local (non-committed) changes will not be factored into this hash.
518 # I think this is OK, since we're really only worried about warning the user
519 # about upstream changes.
520 return self._hooks_project.work_git.rev_parse('HEAD')
521
522 def _GetMustVerb(self):
523 """Return 'must' if the hook is required; 'should' if not."""
524 if self._abort_if_user_denies:
525 return 'must'
526 else:
527 return 'should'
528
529 def _CheckForHookApproval(self):
530 """Check to see whether this hook has been approved.
531
Mike Frysinger40252c22016-08-15 21:23:44 -0400532 We'll accept approval of manifest URLs if they're using secure transports.
533 This way the user can say they trust the manifest hoster. For insecure
534 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800535
536 Note that we ask permission for each individual hook even though we use
537 the hash of all hooks when detecting changes. We'd like the user to be
538 able to approve / deny each hook individually. We only use the hash of all
539 hooks because there is no other easy way to detect changes to local imports.
540
541 Returns:
542 True if this hook is approved to run; False otherwise.
543
544 Raises:
545 HookError: Raised if the user doesn't approve and abort_if_user_denies
546 was passed to the consturctor.
547 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400548 if self._ManifestUrlHasSecureScheme():
549 return self._CheckForHookApprovalManifest()
550 else:
551 return self._CheckForHookApprovalHash()
552
553 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
554 changed_prompt):
555 """Check for approval for a particular attribute and hook.
556
557 Args:
558 subkey: The git config key under [repo.hooks.<hook_type>] to store the
559 last approved string.
560 new_val: The new value to compare against the last approved one.
561 main_prompt: Message to display to the user to ask for approval.
562 changed_prompt: Message explaining why we're re-asking for approval.
563
564 Returns:
565 True if this hook is approved to run; False otherwise.
566
567 Raises:
568 HookError: Raised if the user doesn't approve and abort_if_user_denies
569 was passed to the consturctor.
570 """
Doug Anderson37282b42011-03-04 11:54:18 -0800571 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400572 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800573
Mike Frysinger40252c22016-08-15 21:23:44 -0400574 # Get the last value that the user approved for this hook; may be None.
575 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800576
Mike Frysinger40252c22016-08-15 21:23:44 -0400577 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800578 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400579 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800580 # Approval matched. We're done.
581 return True
582 else:
583 # Give the user a reason why we're prompting, since they last told
584 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400585 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800586 else:
587 prompt = ''
588
589 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
590 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400591 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530592 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900593 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800594
595 # User is doing a one-time approval.
596 if response in ('y', 'yes'):
597 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400598 elif response == 'always':
599 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800600 return True
601
602 # For anything else, we'll assume no approval.
603 if self._abort_if_user_denies:
604 raise HookError('You must allow the %s hook or use --no-verify.' %
605 self._hook_type)
606
607 return False
608
Mike Frysinger40252c22016-08-15 21:23:44 -0400609 def _ManifestUrlHasSecureScheme(self):
610 """Check if the URI for the manifest is a secure transport."""
611 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
612 parse_results = urllib.parse.urlparse(self._manifest_url)
613 return parse_results.scheme in secure_schemes
614
615 def _CheckForHookApprovalManifest(self):
616 """Check whether the user has approved this manifest host.
617
618 Returns:
619 True if this hook is approved to run; False otherwise.
620 """
621 return self._CheckForHookApprovalHelper(
622 'approvedmanifest',
623 self._manifest_url,
624 'Run hook scripts from %s' % (self._manifest_url,),
625 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
626
627 def _CheckForHookApprovalHash(self):
628 """Check whether the user has approved the hooks repo.
629
630 Returns:
631 True if this hook is approved to run; False otherwise.
632 """
633 prompt = ('Repo %s run the script:\n'
634 ' %s\n'
635 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700636 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400637 return self._CheckForHookApprovalHelper(
638 'approvedhash',
639 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700640 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400641 'Scripts have changed since %s was allowed.' % (self._hook_type,))
642
Mike Frysingerf7c51602019-06-18 17:23:39 -0400643 @staticmethod
644 def _ExtractInterpFromShebang(data):
645 """Extract the interpreter used in the shebang.
646
647 Try to locate the interpreter the script is using (ignoring `env`).
648
649 Args:
650 data: The file content of the script.
651
652 Returns:
653 The basename of the main script interpreter, or None if a shebang is not
654 used or could not be parsed out.
655 """
656 firstline = data.splitlines()[:1]
657 if not firstline:
658 return None
659
660 # The format here can be tricky.
661 shebang = firstline[0].strip()
662 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
663 if not m:
664 return None
665
666 # If the using `env`, find the target program.
667 interp = m.group(1)
668 if os.path.basename(interp) == 'env':
669 interp = m.group(2)
670
671 return interp
672
673 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
674 """Execute the hook script through |interp|.
675
676 Note: Support for this feature should be dropped ~Jun 2021.
677
678 Args:
679 interp: The Python program to run.
680 context: Basic Python context to execute the hook inside.
681 kwargs: Arbitrary arguments to pass to the hook script.
682
683 Raises:
684 HookError: When the hooks failed for any reason.
685 """
686 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
687 script = """
688import json, os, sys
689path = '''%(path)s'''
690kwargs = json.loads('''%(kwargs)s''')
691context = json.loads('''%(context)s''')
692sys.path.insert(0, os.path.dirname(path))
693data = open(path).read()
694exec(compile(data, path, 'exec'), context)
695context['main'](**kwargs)
696""" % {
697 'path': self._script_fullpath,
698 'kwargs': json.dumps(kwargs),
699 'context': json.dumps(context),
700 }
701
702 # We pass the script via stdin to avoid OS argv limits. It also makes
703 # unhandled exception tracebacks less verbose/confusing for users.
704 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
705 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
706 proc.communicate(input=script.encode('utf-8'))
707 if proc.returncode:
708 raise HookError('Failed to run %s hook.' % (self._hook_type,))
709
710 def _ExecuteHookViaImport(self, data, context, **kwargs):
711 """Execute the hook code in |data| directly.
712
713 Args:
714 data: The code of the hook to execute.
715 context: Basic Python context to execute the hook inside.
716 kwargs: Arbitrary arguments to pass to the hook script.
717
718 Raises:
719 HookError: When the hooks failed for any reason.
720 """
721 # Exec, storing global context in the context dict. We catch exceptions
722 # and convert to a HookError w/ just the failing traceback.
723 try:
724 exec(compile(data, self._script_fullpath, 'exec'), context)
725 except Exception:
726 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
727 (traceback.format_exc(), self._hook_type))
728
729 # Running the script should have defined a main() function.
730 if 'main' not in context:
731 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
732
733 # Call the main function in the hook. If the hook should cause the
734 # build to fail, it will raise an Exception. We'll catch that convert
735 # to a HookError w/ just the failing traceback.
736 try:
737 context['main'](**kwargs)
738 except Exception:
739 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
740 'above.' % (traceback.format_exc(), self._hook_type))
741
Doug Anderson37282b42011-03-04 11:54:18 -0800742 def _ExecuteHook(self, **kwargs):
743 """Actually execute the given hook.
744
745 This will run the hook's 'main' function in our python interpreter.
746
747 Args:
748 kwargs: Keyword arguments to pass to the hook. These are often specific
749 to the hook type. For instance, pre-upload hooks will contain
750 a project_list.
751 """
752 # Keep sys.path and CWD stashed away so that we can always restore them
753 # upon function exit.
754 orig_path = os.getcwd()
755 orig_syspath = sys.path
756
757 try:
758 # Always run hooks with CWD as topdir.
759 os.chdir(self._topdir)
760
761 # Put the hook dir as the first item of sys.path so hooks can do
762 # relative imports. We want to replace the repo dir as [0] so
763 # hooks can't import repo files.
764 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
765
Mike Frysingerf7c51602019-06-18 17:23:39 -0400766 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500767 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800768
Doug Anderson37282b42011-03-04 11:54:18 -0800769 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
770 # We don't actually want hooks to define their main with this argument--
771 # it's there to remind them that their hook should always take **kwargs.
772 # For instance, a pre-upload hook should be defined like:
773 # def main(project_list, **kwargs):
774 #
775 # This allows us to later expand the API without breaking old hooks.
776 kwargs = kwargs.copy()
777 kwargs['hook_should_take_kwargs'] = True
778
Mike Frysingerf7c51602019-06-18 17:23:39 -0400779 # See what version of python the hook has been written against.
780 data = open(self._script_fullpath).read()
781 interp = self._ExtractInterpFromShebang(data)
782 reexec = False
783 if interp:
784 prog = os.path.basename(interp)
785 if prog.startswith('python2') and sys.version_info.major != 2:
786 reexec = True
787 elif prog.startswith('python3') and sys.version_info.major == 2:
788 reexec = True
789
790 # Attempt to execute the hooks through the requested version of Python.
791 if reexec:
792 try:
793 self._ExecuteHookViaReexec(interp, context, **kwargs)
794 except OSError as e:
795 if e.errno == errno.ENOENT:
796 # We couldn't find the interpreter, so fallback to importing.
797 reexec = False
798 else:
799 raise
800
801 # Run the hook by importing directly.
802 if not reexec:
803 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800804 finally:
805 # Restore sys.path and CWD.
806 sys.path = orig_syspath
807 os.chdir(orig_path)
808
809 def Run(self, user_allows_all_hooks, **kwargs):
810 """Run the hook.
811
812 If the hook doesn't exist (because there is no hooks project or because
813 this particular hook is not enabled), this is a no-op.
814
815 Args:
816 user_allows_all_hooks: If True, we will never prompt about running the
817 hook--we'll just assume it's OK to run it.
818 kwargs: Keyword arguments to pass to the hook. These are often specific
819 to the hook type. For instance, pre-upload hooks will contain
820 a project_list.
821
822 Raises:
823 HookError: If there was a problem finding the hook or the user declined
824 to run a required hook (from _CheckForHookApproval).
825 """
826 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700827 if ((not self._hooks_project) or (self._hook_type not in
828 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800829 return
830
831 # Bail with a nice error if we can't find the hook.
832 if not os.path.isfile(self._script_fullpath):
833 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
834
835 # Make sure the user is OK with running the hook.
836 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
837 return
838
839 # Run the hook with the same version of python we're using.
840 self._ExecuteHook(**kwargs)
841
842
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600844 # These objects can be shared between several working trees.
845 shareable_files = ['description', 'info']
846 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
847 # These objects can only be used by a single working tree.
848 working_tree_files = ['config', 'packed-refs', 'shallow']
849 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700850
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 def __init__(self,
852 manifest,
853 name,
854 remote,
855 gitdir,
David James8d201162013-10-11 17:03:19 -0700856 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 worktree,
858 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700859 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800860 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100861 rebase=True,
862 groups=None,
863 sync_c=False,
864 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900865 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100866 clone_depth=None,
867 upstream=None,
868 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500869 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100870 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900871 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700872 optimized_fetch=False,
873 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800874 """Init a Project object.
875
876 Args:
877 manifest: The XmlManifest object.
878 name: The `name` attribute of manifest.xml's project element.
879 remote: RemoteSpec object specifying its remote's properties.
880 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700881 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800882 worktree: Absolute path of git working tree.
883 relpath: Relative path of git working tree to repo's top directory.
884 revisionExpr: The `revision` attribute of manifest.xml's project element.
885 revisionId: git commit id for checking out.
886 rebase: The `rebase` attribute of manifest.xml's project element.
887 groups: The `groups` attribute of manifest.xml's project element.
888 sync_c: The `sync-c` attribute of manifest.xml's project element.
889 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900890 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800891 upstream: The `upstream` attribute of manifest.xml's project element.
892 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500893 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800894 is_derived: False if the project was explicitly defined in the manifest;
895 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400896 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900897 optimized_fetch: If True, when a project is set to a sha1 revision, only
898 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700899 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800900 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 self.manifest = manifest
902 self.name = name
903 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800904 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700905 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800906 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700907 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800908 else:
909 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700911 self.revisionExpr = revisionExpr
912
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700913 if revisionId is None \
914 and revisionExpr \
915 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700916 self.revisionId = revisionExpr
917 else:
918 self.revisionId = revisionId
919
Mike Pontillod3153822012-02-28 11:53:24 -0800920 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700921 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700922 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800923 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900924 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900925 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700926 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800927 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500928 # NB: Do not use this setting in __init__ to change behavior so that the
929 # manifest.git checkout can inspect & change it after instantiating. See
930 # the XmlManifest init code for more info.
931 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800932 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900933 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800934 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800935
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500938 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500939 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700940 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
941 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800943 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700944 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800945 else:
946 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700947 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700948 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700949 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400950 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700951 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952
Doug Anderson37282b42011-03-04 11:54:18 -0800953 # This will be filled in if a project is later identified to be the
954 # project containing repo hooks.
955 self.enabled_repo_hooks = []
956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800958 def Derived(self):
959 return self.is_derived
960
961 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700963 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964
965 @property
966 def CurrentBranch(self):
967 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400968
969 The branch name omits the 'refs/heads/' prefix.
970 None is returned if the project is on a detached HEAD, or if the work_git is
971 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400973 try:
974 b = self.work_git.GetHead()
975 except NoManifestException:
976 # If the local checkout is in a bad state, don't barf. Let the callers
977 # process this like the head is unreadable.
978 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 if b.startswith(R_HEADS):
980 return b[len(R_HEADS):]
981 return None
982
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700983 def IsRebaseInProgress(self):
984 w = self.worktree
985 g = os.path.join(w, '.git')
986 return os.path.exists(os.path.join(g, 'rebase-apply')) \
987 or os.path.exists(os.path.join(g, 'rebase-merge')) \
988 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200989
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 def IsDirty(self, consider_untracked=True):
991 """Is the working directory modified in some way?
992 """
993 self.work_git.update_index('-q',
994 '--unmerged',
995 '--ignore-missing',
996 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900997 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 return True
999 if self.work_git.DiffZ('diff-files'):
1000 return True
1001 if consider_untracked and self.work_git.LsOthers():
1002 return True
1003 return False
1004
1005 _userident_name = None
1006 _userident_email = None
1007
1008 @property
1009 def UserName(self):
1010 """Obtain the user's personal name.
1011 """
1012 if self._userident_name is None:
1013 self._LoadUserIdentity()
1014 return self._userident_name
1015
1016 @property
1017 def UserEmail(self):
1018 """Obtain the user's email address. This is very likely
1019 to be their Gerrit login.
1020 """
1021 if self._userident_email is None:
1022 self._LoadUserIdentity()
1023 return self._userident_email
1024
1025 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001026 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1027 m = re.compile("^(.*) <([^>]*)> ").match(u)
1028 if m:
1029 self._userident_name = m.group(1)
1030 self._userident_email = m.group(2)
1031 else:
1032 self._userident_name = ''
1033 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
1035 def GetRemote(self, name):
1036 """Get the configuration for a single remote.
1037 """
1038 return self.config.GetRemote(name)
1039
1040 def GetBranch(self, name):
1041 """Get the configuration for a single branch.
1042 """
1043 return self.config.GetBranch(name)
1044
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001045 def GetBranches(self):
1046 """Get all existing local branches.
1047 """
1048 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001050 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001051
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301052 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001053 if name.startswith(R_HEADS):
1054 name = name[len(R_HEADS):]
1055 b = self.GetBranch(name)
1056 b.current = name == current
1057 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001058 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001059 heads[name] = b
1060
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301061 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001062 if name.startswith(R_PUB):
1063 name = name[len(R_PUB):]
1064 b = heads.get(name)
1065 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001066 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001067
1068 return heads
1069
Colin Cross5acde752012-03-28 20:15:45 -07001070 def MatchesGroups(self, manifest_groups):
1071 """Returns true if the manifest groups specified at init should cause
1072 this project to be synced.
1073 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001074 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001075
1076 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001077 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001078 manifest_groups: "-group1,group2"
1079 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001080
1081 The special manifest group "default" will match any project that
1082 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001083 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001084 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001085 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001086 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001087 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001088
Conley Owens971de8e2012-04-16 10:36:08 -07001089 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001090 for group in expanded_manifest_groups:
1091 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001092 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001093 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001094 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001095
Conley Owens971de8e2012-04-16 10:36:08 -07001096 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001098# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001099 def UncommitedFiles(self, get_all=True):
1100 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001102 Args:
1103 get_all: a boolean, if True - get information about all different
1104 uncommitted files. If False - return as soon as any kind of
1105 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001106 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001108 self.work_git.update_index('-q',
1109 '--unmerged',
1110 '--ignore-missing',
1111 '--refresh')
1112 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001113 details.append("rebase in progress")
1114 if not get_all:
1115 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001116
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001117 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1118 if changes:
1119 details.extend(changes)
1120 if not get_all:
1121 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001122
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001123 changes = self.work_git.DiffZ('diff-files').keys()
1124 if changes:
1125 details.extend(changes)
1126 if not get_all:
1127 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001128
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001129 changes = self.work_git.LsOthers()
1130 if changes:
1131 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001132
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001133 return details
1134
1135 def HasChanges(self):
1136 """Returns true if there are uncommitted changes.
1137 """
1138 if self.UncommitedFiles(get_all=False):
1139 return True
1140 else:
1141 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001142
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001143 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001145
1146 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001147 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001148 quiet: If True then only print the project name. Do not print
1149 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001151 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001152 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001153 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001154 print(file=output_redir)
1155 print('project %s/' % self.relpath, file=output_redir)
1156 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157 return
1158
1159 self.work_git.update_index('-q',
1160 '--unmerged',
1161 '--ignore-missing',
1162 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001163 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1165 df = self.work_git.DiffZ('diff-files')
1166 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001167 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001168 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169
1170 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001171 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001172 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001173 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001175 if quiet:
1176 out.nl()
1177 return 'DIRTY'
1178
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 branch = self.CurrentBranch
1180 if branch is None:
1181 out.nobranch('(*** NO BRANCH ***)')
1182 else:
1183 out.branch('branch %s', branch)
1184 out.nl()
1185
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001186 if rb:
1187 out.important('prior sync failed; rebase still in progress')
1188 out.nl()
1189
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 paths = list()
1191 paths.extend(di.keys())
1192 paths.extend(df.keys())
1193 paths.extend(do)
1194
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301195 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001196 try:
1197 i = di[p]
1198 except KeyError:
1199 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001201 try:
1202 f = df[p]
1203 except KeyError:
1204 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001205
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001206 if i:
1207 i_status = i.status.upper()
1208 else:
1209 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001211 if f:
1212 f_status = f.status.lower()
1213 else:
1214 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
1216 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001217 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001218 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219 else:
1220 line = ' %s%s\t%s' % (i_status, f_status, p)
1221
1222 if i and not f:
1223 out.added('%s', line)
1224 elif (i and f) or (not i and f):
1225 out.changed('%s', line)
1226 elif not i and not f:
1227 out.untracked('%s', line)
1228 else:
1229 out.write('%s', line)
1230 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001231
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001232 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233
pelyad67872d2012-03-28 14:49:58 +03001234 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 """Prints the status of the repository to stdout.
1236 """
1237 out = DiffColoring(self.config)
1238 cmd = ['diff']
1239 if out.is_on:
1240 cmd.append('--color')
1241 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001242 if absolute_paths:
1243 cmd.append('--src-prefix=a/%s/' % self.relpath)
1244 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001246 try:
1247 p = GitCommand(self,
1248 cmd,
1249 capture_stdout=True,
1250 capture_stderr=True)
1251 except GitError as e:
1252 out.nl()
1253 out.project('project %s/' % self.relpath)
1254 out.nl()
1255 out.fail('%s', str(e))
1256 out.nl()
1257 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258 has_diff = False
1259 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001260 if not hasattr(line, 'encode'):
1261 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 if not has_diff:
1263 out.nl()
1264 out.project('project %s/' % self.relpath)
1265 out.nl()
1266 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001267 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001268 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001269
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001270# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272 """Was the branch published (uploaded) for code review?
1273 If so, returns the SHA-1 hash of the last published
1274 state for the branch.
1275 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001277 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001278 try:
1279 return self.bare_git.rev_parse(key)
1280 except GitError:
1281 return None
1282 else:
1283 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001284 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001285 except KeyError:
1286 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287
David Pursehouse8a68ff92012-09-24 12:15:13 +09001288 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 """Prunes any stale published refs.
1290 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 if all_refs is None:
1292 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 heads = set()
1294 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301295 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 if name.startswith(R_HEADS):
1297 heads.add(name)
1298 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001299 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301301 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 n = name[len(R_PUB):]
1303 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001306 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 """List any branches which can be uploaded for review.
1308 """
1309 heads = {}
1310 pubed = {}
1311
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301312 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001314 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001316 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317
1318 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301319 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001320 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001322 if selected_branch and branch != selected_branch:
1323 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001325 rb = self.GetUploadableBranch(branch)
1326 if rb:
1327 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 return ready
1329
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001330 def GetUploadableBranch(self, branch_name):
1331 """Get a single uploadable branch, or None.
1332 """
1333 branch = self.GetBranch(branch_name)
1334 base = branch.LocalMerge
1335 if branch.LocalMerge:
1336 rb = ReviewableBranch(self, branch, base)
1337 if rb.commits:
1338 return rb
1339 return None
1340
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001341 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001342 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001343 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001344 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001345 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001346 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001347 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001348 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001349 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001350 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001351 validate_certs=True,
1352 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 """Uploads the named branch for code review.
1354 """
1355 if branch is None:
1356 branch = self.CurrentBranch
1357 if branch is None:
1358 raise GitError('not currently on a branch')
1359
1360 branch = self.GetBranch(branch)
1361 if not branch.LocalMerge:
1362 raise GitError('branch %s does not track a remote' % branch.name)
1363 if not branch.remote.review:
1364 raise GitError('remote %s has no review url' % branch.remote.name)
1365
Bryan Jacobsf609f912013-05-06 13:36:24 -04001366 if dest_branch is None:
1367 dest_branch = self.dest_branch
1368 if dest_branch is None:
1369 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 if not dest_branch.startswith(R_HEADS):
1371 dest_branch = R_HEADS + dest_branch
1372
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001373 if not branch.remote.projectname:
1374 branch.remote.projectname = self.name
1375 branch.remote.Save()
1376
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001377 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001378 if url is None:
1379 raise UploadError('review not configured')
1380 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001381 if dryrun:
1382 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001383
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001384 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001385 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001386
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001387 for push_option in (push_options or []):
1388 cmd.append('-o')
1389 cmd.append(push_option)
1390
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001391 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001392
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001393 if dest_branch.startswith(R_HEADS):
1394 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001395
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001396 upload_type = 'for'
1397 if draft:
1398 upload_type = 'drafts'
1399
1400 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1401 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001402 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001403 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001404 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001405 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001406
David Pursehousef25a3702018-11-14 19:01:22 -08001407 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001408 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001409 if notify:
1410 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001411 if private:
1412 opts += ['private']
1413 if wip:
1414 opts += ['wip']
1415 if opts:
1416 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001417 cmd.append(ref_spec)
1418
Anthony King7bdac712014-07-16 12:56:40 +01001419 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001420 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
1422 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1423 self.bare_git.UpdateRef(R_PUB + branch.name,
1424 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001425 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001427# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001428 def _ExtractArchive(self, tarpath, path=None):
1429 """Extract the given tar on its current location
1430
1431 Args:
1432 - tarpath: The path to the actual tar file
1433
1434 """
1435 try:
1436 with tarfile.open(tarpath, 'r') as tar:
1437 tar.extractall(path=path)
1438 return True
1439 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001440 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001441 return False
1442
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001443 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001444 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001445 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001446 is_new=None,
1447 current_branch_only=False,
1448 force_sync=False,
1449 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001450 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001451 archive=False,
1452 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001453 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001454 submodules=False,
1455 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456 """Perform only the network IO portion of the sync process.
1457 Local working directory/branch state is not affected.
1458 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001459 if archive and not isinstance(self, MetaProject):
1460 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001461 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001462 return False
1463
1464 name = self.relpath.replace('\\', '/')
1465 name = name.replace('/', '_')
1466 tarpath = '%s.tar' % name
1467 topdir = self.manifest.topdir
1468
1469 try:
1470 self._FetchArchive(tarpath, cwd=topdir)
1471 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001472 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001473 return False
1474
1475 # From now on, we only need absolute tarpath
1476 tarpath = os.path.join(topdir, tarpath)
1477
1478 if not self._ExtractArchive(tarpath, path=topdir):
1479 return False
1480 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001481 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001482 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001483 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001484 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001485 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001486 if is_new is None:
1487 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001488 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001489 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001490 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001491 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493
1494 if is_new:
1495 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1496 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001497 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001498 # This works for both absolute and relative alternate directories.
1499 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500 except IOError:
1501 alt_dir = None
1502 else:
1503 alt_dir = None
1504
Mike Frysingere50b6a72020-02-19 01:45:48 -05001505 if (clone_bundle
1506 and alt_dir is None
1507 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001508 is_new = False
1509
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001510 if not current_branch_only:
1511 if self.sync_c:
1512 current_branch_only = True
1513 elif not self.manifest._loaded:
1514 # Manifest cannot check defaults until it syncs.
1515 current_branch_only = False
1516 elif self.manifest.default.sync_c:
1517 current_branch_only = True
1518
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001519 if not self.sync_tags:
1520 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001521
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001522 if self.clone_depth:
1523 depth = self.clone_depth
1524 else:
1525 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1526
Mike Frysinger521d01b2020-02-17 01:51:49 -05001527 # See if we can skip the network fetch entirely.
1528 if not (optimized_fetch and
1529 (ID_RE.match(self.revisionExpr) and
1530 self._CheckForImmutableRevision())):
1531 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001532 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1533 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001534 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001535 submodules=submodules, force_sync=force_sync,
1536 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001537 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001538
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001539 mp = self.manifest.manifestProject
1540 dissociate = mp.config.GetBoolean('repo.dissociate')
1541 if dissociate:
1542 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1543 if os.path.exists(alternates_file):
1544 cmd = ['repack', '-a', '-d']
1545 if GitCommand(self, cmd, bare=True).Wait() != 0:
1546 return False
1547 platform_utils.remove(alternates_file)
1548
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001549 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001550 self._InitMRef()
1551 else:
1552 self._InitMirrorHead()
1553 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001554 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001555 except OSError:
1556 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001557 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001558
1559 def PostRepoUpgrade(self):
1560 self._InitHooks()
1561
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001562 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001563 if self.manifest.isGitcClient:
1564 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001565 for copyfile in self.copyfiles:
1566 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001567 for linkfile in self.linkfiles:
1568 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569
Julien Camperguedd654222014-01-09 16:21:37 +01001570 def GetCommitRevisionId(self):
1571 """Get revisionId of a commit.
1572
1573 Use this method instead of GetRevisionId to get the id of the commit rather
1574 than the id of the current git object (for example, a tag)
1575
1576 """
1577 if not self.revisionExpr.startswith(R_TAGS):
1578 return self.GetRevisionId(self._allrefs)
1579
1580 try:
1581 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1582 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001583 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1584 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001585
David Pursehouse8a68ff92012-09-24 12:15:13 +09001586 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001587 if self.revisionId:
1588 return self.revisionId
1589
1590 rem = self.GetRemote(self.remote.name)
1591 rev = rem.ToLocal(self.revisionExpr)
1592
David Pursehouse8a68ff92012-09-24 12:15:13 +09001593 if all_refs is not None and rev in all_refs:
1594 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001595
1596 try:
1597 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1598 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001599 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1600 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001601
Martin Kellye4e94d22017-03-21 16:05:12 -07001602 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 """Perform only the local IO portion of the sync process.
1604 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001606 if not os.path.exists(self.gitdir):
1607 syncbuf.fail(self,
1608 'Cannot checkout %s due to missing network sync; Run '
1609 '`repo sync -n %s` first.' %
1610 (self.name, self.name))
1611 return
1612
Martin Kellye4e94d22017-03-21 16:05:12 -07001613 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001614 all_refs = self.bare_ref.all
1615 self.CleanPublishedCache(all_refs)
1616 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001617
David Pursehouse1d947b32012-10-25 12:23:11 +09001618 def _doff():
1619 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001620 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001621
Martin Kellye4e94d22017-03-21 16:05:12 -07001622 def _dosubmodules():
1623 self._SyncSubmodules(quiet=True)
1624
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001625 head = self.work_git.GetHead()
1626 if head.startswith(R_HEADS):
1627 branch = head[len(R_HEADS):]
1628 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001629 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001630 except KeyError:
1631 head = None
1632 else:
1633 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001634
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001635 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001636 # Currently on a detached HEAD. The user is assumed to
1637 # not have any local modifications worth worrying about.
1638 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001639 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001640 syncbuf.fail(self, _PriorSyncFailedError())
1641 return
1642
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001643 if head == revid:
1644 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001645 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001646 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001647 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001648 # The copy/linkfile config may have changed.
1649 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001650 return
1651 else:
1652 lost = self._revlist(not_rev(revid), HEAD)
1653 if lost:
1654 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001655
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001657 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001658 if submodules:
1659 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001660 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001661 syncbuf.fail(self, e)
1662 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001663 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001664 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001666 if head == revid:
1667 # No changes; don't do anything further.
1668 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001669 # The copy/linkfile config may have changed.
1670 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001671 return
1672
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001673 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001674
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001675 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001677 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001679 syncbuf.info(self,
1680 "leaving %s; does not track upstream",
1681 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001683 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001684 if submodules:
1685 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001686 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001687 syncbuf.fail(self, e)
1688 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001689 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001690 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001692 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001693
1694 # See if we can perform a fast forward merge. This can happen if our
1695 # branch isn't in the exact same state as we last published.
1696 try:
1697 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1698 # Skip the published logic.
1699 pub = False
1700 except GitError:
1701 pub = self.WasPublished(branch.name, all_refs)
1702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001704 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 if not_merged:
1706 if upstream_gain:
1707 # The user has published this branch and some of those
1708 # commits are not yet merged upstream. We do not want
1709 # to rewrite the published commits so we punt.
1710 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001711 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001712 "branch %s is published (but not merged) and is now "
1713 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001714 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001715 elif pub == head:
1716 # All published commits are merged, and thus we are a
1717 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001718 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001719 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001720 if submodules:
1721 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001722 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001724 # Examine the local commits not in the remote. Find the
1725 # last one attributed to this user, if any.
1726 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001727 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001728 last_mine = None
1729 cnt_mine = 0
1730 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001731 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001732 if committer_email == self.UserEmail:
1733 last_mine = commit_id
1734 cnt_mine += 1
1735
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001736 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001737 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738
1739 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001740 syncbuf.fail(self, _DirtyError())
1741 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001743 # If the upstream switched on us, warn the user.
1744 #
1745 if branch.merge != self.revisionExpr:
1746 if branch.merge and self.revisionExpr:
1747 syncbuf.info(self,
1748 'manifest switched %s...%s',
1749 branch.merge,
1750 self.revisionExpr)
1751 elif branch.merge:
1752 syncbuf.info(self,
1753 'manifest no longer tracks %s',
1754 branch.merge)
1755
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001756 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001758 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001759 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001760 syncbuf.info(self,
1761 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001762 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001764 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001765 if not ID_RE.match(self.revisionExpr):
1766 # in case of manifest sync the revisionExpr might be a SHA1
1767 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001768 if not branch.merge.startswith('refs/'):
1769 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770 branch.Save()
1771
Mike Pontillod3153822012-02-28 11:53:24 -08001772 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001773 def _docopyandlink():
1774 self._CopyAndLinkFiles()
1775
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001776 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001777 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001778 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001779 if submodules:
1780 syncbuf.later2(self, _dosubmodules)
1781 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001782 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001784 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001785 if submodules:
1786 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001787 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001788 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001789 syncbuf.fail(self, e)
1790 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001792 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001793 if submodules:
1794 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001795
Mike Frysingere6a202f2019-08-02 15:57:57 -04001796 def AddCopyFile(self, src, dest, topdir):
1797 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798
Mike Frysingere6a202f2019-08-02 15:57:57 -04001799 No filesystem changes occur here. Actual copying happens later on.
1800
1801 Paths should have basic validation run on them before being queued.
1802 Further checking will be handled when the actual copy happens.
1803 """
1804 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1805
1806 def AddLinkFile(self, src, dest, topdir):
1807 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1808
1809 No filesystem changes occur here. Actual linking happens later on.
1810
1811 Paths should have basic validation run on them before being queued.
1812 Further checking will be handled when the actual link happens.
1813 """
1814 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001815
James W. Mills24c13082012-04-12 15:04:13 -05001816 def AddAnnotation(self, name, value, keep):
1817 self.annotations.append(_Annotation(name, value, keep))
1818
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001819 def DownloadPatchSet(self, change_id, patch_id):
1820 """Download a single patch set of a single change to FETCH_HEAD.
1821 """
1822 remote = self.GetRemote(self.remote.name)
1823
1824 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001825 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001826 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001827 if GitCommand(self, cmd, bare=True).Wait() != 0:
1828 return None
1829 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001830 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001831 change_id,
1832 patch_id,
1833 self.bare_git.rev_parse('FETCH_HEAD'))
1834
Mike Frysingerc0d18662020-02-19 19:19:18 -05001835 def DeleteWorktree(self, quiet=False, force=False):
1836 """Delete the source checkout and any other housekeeping tasks.
1837
1838 This currently leaves behind the internal .repo/ cache state. This helps
1839 when switching branches or manifest changes get reverted as we don't have
1840 to redownload all the git objects. But we should do some GC at some point.
1841
1842 Args:
1843 quiet: Whether to hide normal messages.
1844 force: Always delete tree even if dirty.
1845
1846 Returns:
1847 True if the worktree was completely cleaned out.
1848 """
1849 if self.IsDirty():
1850 if force:
1851 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1852 (self.relpath,), file=sys.stderr)
1853 else:
1854 print('error: %s: Cannot remove project: uncommitted changes are '
1855 'present.\n' % (self.relpath,), file=sys.stderr)
1856 return False
1857
1858 if not quiet:
1859 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1860
1861 # Unlock and delink from the main worktree. We don't use git's worktree
1862 # remove because it will recursively delete projects -- we handle that
1863 # ourselves below. https://crbug.com/git/48
1864 if self.use_git_worktrees:
1865 needle = platform_utils.realpath(self.gitdir)
1866 # Find the git worktree commondir under .repo/worktrees/.
1867 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1868 assert output.startswith('worktree '), output
1869 commondir = output[9:]
1870 # Walk each of the git worktrees to see where they point.
1871 configs = os.path.join(commondir, 'worktrees')
1872 for name in os.listdir(configs):
1873 gitdir = os.path.join(configs, name, 'gitdir')
1874 with open(gitdir) as fp:
1875 relpath = fp.read().strip()
1876 # Resolve the checkout path and see if it matches this project.
1877 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1878 if fullpath == needle:
1879 platform_utils.rmtree(os.path.join(configs, name))
1880
1881 # Delete the .git directory first, so we're less likely to have a partially
1882 # working git repository around. There shouldn't be any git projects here,
1883 # so rmtree works.
1884
1885 # Try to remove plain files first in case of git worktrees. If this fails
1886 # for any reason, we'll fall back to rmtree, and that'll display errors if
1887 # it can't remove things either.
1888 try:
1889 platform_utils.remove(self.gitdir)
1890 except OSError:
1891 pass
1892 try:
1893 platform_utils.rmtree(self.gitdir)
1894 except OSError as e:
1895 if e.errno != errno.ENOENT:
1896 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1897 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1898 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1899 return False
1900
1901 # Delete everything under the worktree, except for directories that contain
1902 # another git project.
1903 dirs_to_remove = []
1904 failed = False
1905 for root, dirs, files in platform_utils.walk(self.worktree):
1906 for f in files:
1907 path = os.path.join(root, f)
1908 try:
1909 platform_utils.remove(path)
1910 except OSError as e:
1911 if e.errno != errno.ENOENT:
1912 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1913 failed = True
1914 dirs[:] = [d for d in dirs
1915 if not os.path.lexists(os.path.join(root, d, '.git'))]
1916 dirs_to_remove += [os.path.join(root, d) for d in dirs
1917 if os.path.join(root, d) not in dirs_to_remove]
1918 for d in reversed(dirs_to_remove):
1919 if platform_utils.islink(d):
1920 try:
1921 platform_utils.remove(d)
1922 except OSError as e:
1923 if e.errno != errno.ENOENT:
1924 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1925 failed = True
1926 elif not platform_utils.listdir(d):
1927 try:
1928 platform_utils.rmdir(d)
1929 except OSError as e:
1930 if e.errno != errno.ENOENT:
1931 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1932 failed = True
1933 if failed:
1934 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1935 file=sys.stderr)
1936 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1937 return False
1938
1939 # Try deleting parent dirs if they are empty.
1940 path = self.worktree
1941 while path != self.manifest.topdir:
1942 try:
1943 platform_utils.rmdir(path)
1944 except OSError as e:
1945 if e.errno != errno.ENOENT:
1946 break
1947 path = os.path.dirname(path)
1948
1949 return True
1950
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001951# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001952 def GetHeadPath(self):
1953 """Return the full path to the HEAD ref."""
1954 dotgit = os.path.join(self.worktree, '.git')
1955 if os.path.isfile(dotgit):
1956 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1957 with open(dotgit) as fp:
1958 setting = fp.read()
1959 assert setting.startswith('gitdir:')
1960 gitdir = setting.split(':', 1)[1].strip()
1961 dotgit = os.path.join(self.worktree, gitdir)
1962 return os.path.join(dotgit, HEAD)
1963
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001964 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001965 """Create a new branch off the manifest's revision.
1966 """
Simran Basib9a1b732015-08-20 12:19:28 -07001967 if not branch_merge:
1968 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001969 head = self.work_git.GetHead()
1970 if head == (R_HEADS + name):
1971 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972
David Pursehouse8a68ff92012-09-24 12:15:13 +09001973 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001974 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001975 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001976 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001977 capture_stdout=True,
1978 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001979
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001980 branch = self.GetBranch(name)
1981 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001982 branch.merge = branch_merge
1983 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1984 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001985
1986 if revision is None:
1987 revid = self.GetRevisionId(all_refs)
1988 else:
1989 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001990
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001991 if head.startswith(R_HEADS):
1992 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001993 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001994 except KeyError:
1995 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001996 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001997 ref = R_HEADS + name
1998 self.work_git.update_ref(ref, revid)
1999 self.work_git.symbolic_ref(HEAD, ref)
2000 branch.Save()
2001 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002002
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002003 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002004 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002005 capture_stdout=True,
2006 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002007 branch.Save()
2008 return True
2009 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002010
Wink Saville02d79452009-04-10 13:01:24 -07002011 def CheckoutBranch(self, name):
2012 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002013
2014 Args:
2015 name: The name of the branch to checkout.
2016
2017 Returns:
2018 True if the checkout succeeded; False if it didn't; None if the branch
2019 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002020 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002021 rev = R_HEADS + name
2022 head = self.work_git.GetHead()
2023 if head == rev:
2024 # Already on the branch
2025 #
2026 return True
Wink Saville02d79452009-04-10 13:01:24 -07002027
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002029 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002030 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002031 except KeyError:
2032 # Branch does not exist in this project
2033 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002034 return None
Wink Saville02d79452009-04-10 13:01:24 -07002035
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002036 if head.startswith(R_HEADS):
2037 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002038 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002039 except KeyError:
2040 head = None
2041
2042 if head == revid:
2043 # Same revision; just update HEAD to point to the new
2044 # target branch, but otherwise take no other action.
2045 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05002046 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002047 return True
2048
2049 return GitCommand(self,
2050 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002051 capture_stdout=True,
2052 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002053
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002054 def AbandonBranch(self, name):
2055 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002056
2057 Args:
2058 name: The name of the branch to abandon.
2059
2060 Returns:
2061 True if the abandon succeeded; False if it didn't; None if the branch
2062 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002063 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002064 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002065 all_refs = self.bare_ref.all
2066 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002067 # Doesn't exist
2068 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002069
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002070 head = self.work_git.GetHead()
2071 if head == rev:
2072 # We can't destroy the branch while we are sitting
2073 # on it. Switch to a detached HEAD.
2074 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002075 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002076
David Pursehouse8a68ff92012-09-24 12:15:13 +09002077 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002078 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05002079 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002080 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002081 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002082
2083 return GitCommand(self,
2084 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002085 capture_stdout=True,
2086 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002087
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002088 def PruneHeads(self):
2089 """Prune any topic branches already merged into upstream.
2090 """
2091 cb = self.CurrentBranch
2092 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002093 left = self._allrefs
2094 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002095 if name.startswith(R_HEADS):
2096 name = name[len(R_HEADS):]
2097 if cb is None or name != cb:
2098 kill.append(name)
2099
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002100 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101 if cb is not None \
2102 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002103 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104 self.work_git.DetachHead(HEAD)
2105 kill.append(cb)
2106
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002108 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002110 try:
2111 self.bare_git.DetachHead(rev)
2112
2113 b = ['branch', '-d']
2114 b.extend(kill)
2115 b = GitCommand(self, b, bare=True,
2116 capture_stdout=True,
2117 capture_stderr=True)
2118 b.Wait()
2119 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002120 if ID_RE.match(old):
2121 self.bare_git.DetachHead(old)
2122 else:
2123 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002124 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002125
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002126 for branch in kill:
2127 if (R_HEADS + branch) not in left:
2128 self.CleanPublishedCache()
2129 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002130
2131 if cb and cb not in kill:
2132 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002133 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134
2135 kept = []
2136 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002137 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 branch = self.GetBranch(branch)
2139 base = branch.LocalMerge
2140 if not base:
2141 base = rev
2142 kept.append(ReviewableBranch(self, branch, base))
2143 return kept
2144
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002145# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002146 def GetRegisteredSubprojects(self):
2147 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002148
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002149 def rec(subprojects):
2150 if not subprojects:
2151 return
2152 result.extend(subprojects)
2153 for p in subprojects:
2154 rec(p.subprojects)
2155 rec(self.subprojects)
2156 return result
2157
2158 def _GetSubmodules(self):
2159 # Unfortunately we cannot call `git submodule status --recursive` here
2160 # because the working tree might not exist yet, and it cannot be used
2161 # without a working tree in its current implementation.
2162
2163 def get_submodules(gitdir, rev):
2164 # Parse .gitmodules for submodule sub_paths and sub_urls
2165 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2166 if not sub_paths:
2167 return []
2168 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2169 # revision of submodule repository
2170 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2171 submodules = []
2172 for sub_path, sub_url in zip(sub_paths, sub_urls):
2173 try:
2174 sub_rev = sub_revs[sub_path]
2175 except KeyError:
2176 # Ignore non-exist submodules
2177 continue
2178 submodules.append((sub_rev, sub_path, sub_url))
2179 return submodules
2180
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002181 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2182 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002183
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002184 def parse_gitmodules(gitdir, rev):
2185 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2186 try:
Anthony King7bdac712014-07-16 12:56:40 +01002187 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2188 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002189 except GitError:
2190 return [], []
2191 if p.Wait() != 0:
2192 return [], []
2193
2194 gitmodules_lines = []
2195 fd, temp_gitmodules_path = tempfile.mkstemp()
2196 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002197 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002198 os.close(fd)
2199 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002200 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2201 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002202 if p.Wait() != 0:
2203 return [], []
2204 gitmodules_lines = p.stdout.split('\n')
2205 except GitError:
2206 return [], []
2207 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002208 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002209
2210 names = set()
2211 paths = {}
2212 urls = {}
2213 for line in gitmodules_lines:
2214 if not line:
2215 continue
2216 m = re_path.match(line)
2217 if m:
2218 names.add(m.group(1))
2219 paths[m.group(1)] = m.group(2)
2220 continue
2221 m = re_url.match(line)
2222 if m:
2223 names.add(m.group(1))
2224 urls[m.group(1)] = m.group(2)
2225 continue
2226 names = sorted(names)
2227 return ([paths.get(name, '') for name in names],
2228 [urls.get(name, '') for name in names])
2229
2230 def git_ls_tree(gitdir, rev, paths):
2231 cmd = ['ls-tree', rev, '--']
2232 cmd.extend(paths)
2233 try:
Anthony King7bdac712014-07-16 12:56:40 +01002234 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2235 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002236 except GitError:
2237 return []
2238 if p.Wait() != 0:
2239 return []
2240 objects = {}
2241 for line in p.stdout.split('\n'):
2242 if not line.strip():
2243 continue
2244 object_rev, object_path = line.split()[2:4]
2245 objects[object_path] = object_rev
2246 return objects
2247
2248 try:
2249 rev = self.GetRevisionId()
2250 except GitError:
2251 return []
2252 return get_submodules(self.gitdir, rev)
2253
2254 def GetDerivedSubprojects(self):
2255 result = []
2256 if not self.Exists:
2257 # If git repo does not exist yet, querying its submodules will
2258 # mess up its states; so return here.
2259 return result
2260 for rev, path, url in self._GetSubmodules():
2261 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002262 relpath, worktree, gitdir, objdir = \
2263 self.manifest.GetSubprojectPaths(self, name, path)
2264 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002265 if project:
2266 result.extend(project.GetDerivedSubprojects())
2267 continue
David James8d201162013-10-11 17:03:19 -07002268
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002269 if url.startswith('..'):
2270 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002271 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002272 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002273 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002274 review=self.remote.review,
2275 revision=self.remote.revision)
2276 subproject = Project(manifest=self.manifest,
2277 name=name,
2278 remote=remote,
2279 gitdir=gitdir,
2280 objdir=objdir,
2281 worktree=worktree,
2282 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002283 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002284 revisionId=rev,
2285 rebase=self.rebase,
2286 groups=self.groups,
2287 sync_c=self.sync_c,
2288 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002289 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002290 parent=self,
2291 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002292 result.append(subproject)
2293 result.extend(subproject.GetDerivedSubprojects())
2294 return result
2295
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002296# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002297 def EnableRepositoryExtension(self, key, value='true', version=1):
2298 """Enable git repository extension |key| with |value|.
2299
2300 Args:
2301 key: The extension to enabled. Omit the "extensions." prefix.
2302 value: The value to use for the extension.
2303 version: The minimum git repository version needed.
2304 """
2305 # Make sure the git repo version is new enough already.
2306 found_version = self.config.GetInt('core.repositoryFormatVersion')
2307 if found_version is None:
2308 found_version = 0
2309 if found_version < version:
2310 self.config.SetString('core.repositoryFormatVersion', str(version))
2311
2312 # Enable the extension!
2313 self.config.SetString('extensions.%s' % (key,), value)
2314
Zac Livingstone4332262017-06-16 08:56:09 -06002315 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002316 try:
2317 # if revision (sha or tag) is not present then following function
2318 # throws an error.
2319 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2320 return True
2321 except GitError:
2322 # There is no such persistent revision. We have to fetch it.
2323 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002324
Julien Campergue335f5ef2013-10-16 11:02:35 +02002325 def _FetchArchive(self, tarpath, cwd=None):
2326 cmd = ['archive', '-v', '-o', tarpath]
2327 cmd.append('--remote=%s' % self.remote.url)
2328 cmd.append('--prefix=%s/' % self.relpath)
2329 cmd.append(self.revisionExpr)
2330
2331 command = GitCommand(self, cmd, cwd=cwd,
2332 capture_stdout=True,
2333 capture_stderr=True)
2334
2335 if command.Wait() != 0:
2336 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2337
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002338 def _RemoteFetch(self, name=None,
2339 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002340 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002341 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002342 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002343 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002344 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002345 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002346 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002347 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002348 force_sync=False,
2349 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002350
2351 is_sha1 = False
2352 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002353 # The depth should not be used when fetching to a mirror because
2354 # it will result in a shallow repository that cannot be cloned or
2355 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002356 # The repo project should also never be synced with partial depth.
2357 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2358 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002359
Shawn Pearce69e04d82014-01-29 12:48:54 -08002360 if depth:
2361 current_branch_only = True
2362
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002363 if ID_RE.match(self.revisionExpr) is not None:
2364 is_sha1 = True
2365
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002366 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002367 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002368 # this is a tag and its sha1 value should never change
2369 tag_name = self.revisionExpr[len(R_TAGS):]
2370
2371 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002372 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002373 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002374 print('Skipped fetching project %s (already have persistent ref)'
2375 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002376 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002377 if is_sha1 and not depth:
2378 # When syncing a specific commit and --depth is not set:
2379 # * if upstream is explicitly specified and is not a sha1, fetch only
2380 # upstream as users expect only upstream to be fetch.
2381 # Note: The commit might not be in upstream in which case the sync
2382 # will fail.
2383 # * otherwise, fetch all branches to make sure we end up with the
2384 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002385 if self.upstream:
2386 current_branch_only = not ID_RE.match(self.upstream)
2387 else:
2388 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002389
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002390 if not name:
2391 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002392
2393 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002394 remote = self.GetRemote(name)
2395 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002396 ssh_proxy = True
2397
Shawn O. Pearce88443382010-10-08 10:02:09 +02002398 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002399 if alt_dir and 'objects' == os.path.basename(alt_dir):
2400 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002401 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2402 remote = self.GetRemote(name)
2403
David Pursehouse8a68ff92012-09-24 12:15:13 +09002404 all_refs = self.bare_ref.all
2405 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406 tmp = set()
2407
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302408 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002409 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002410 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002411 all_refs[r] = ref_id
2412 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002413 continue
2414
David Pursehouse8a68ff92012-09-24 12:15:13 +09002415 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002416 continue
2417
David Pursehouse8a68ff92012-09-24 12:15:13 +09002418 r = 'refs/_alt/%s' % ref_id
2419 all_refs[r] = ref_id
2420 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002421 tmp.add(r)
2422
heping3d7bbc92017-04-12 19:51:47 +08002423 tmp_packed_lines = []
2424 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002425
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302426 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002427 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002428 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002429 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002430 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002431
heping3d7bbc92017-04-12 19:51:47 +08002432 tmp_packed = ''.join(tmp_packed_lines)
2433 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002434 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002435 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002436 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002437
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002438 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002439
Xin Li745be2e2019-06-03 11:24:30 -07002440 if clone_filter:
2441 git_require((2, 19, 0), fail=True, msg='partial clones')
2442 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002443 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002444
Conley Owensf97e8382015-01-21 11:12:46 -08002445 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002446 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002447 else:
2448 # If this repo has shallow objects, then we don't know which refs have
2449 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2450 # do this with projects that don't have shallow objects, since it is less
2451 # efficient.
2452 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2453 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002454
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002455 if quiet:
2456 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002457 if not self.worktree:
2458 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002459 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002460
Mike Frysingere57f1142019-03-18 21:27:54 -04002461 if force_sync:
2462 cmd.append('--force')
2463
David Pursehouse74cfd272015-10-14 10:50:15 +09002464 if prune:
2465 cmd.append('--prune')
2466
Martin Kellye4e94d22017-03-21 16:05:12 -07002467 if submodules:
2468 cmd.append('--recurse-submodules=on-demand')
2469
Kuang-che Wu6856f982019-11-25 12:37:55 +08002470 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002471 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002472 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002473 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002474 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002475 spec.append('tag')
2476 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002477
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302478 if self.manifest.IsMirror and not current_branch_only:
2479 branch = None
2480 else:
2481 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002482 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002483 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002484 # Shallow checkout of a specific commit, fetch from that commit and not
2485 # the heads only as the commit might be deeper in the history.
2486 spec.append(branch)
2487 else:
2488 if is_sha1:
2489 branch = self.upstream
2490 if branch is not None and branch.strip():
2491 if not branch.startswith('refs/'):
2492 branch = R_HEADS + branch
2493 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2494
2495 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2496 # whole repo.
2497 if self.manifest.IsMirror and not spec:
2498 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2499
2500 # If using depth then we should not get all the tags since they may
2501 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002502 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002503 cmd.append('--no-tags')
2504 else:
2505 cmd.append('--tags')
2506 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2507
Conley Owens80b87fe2014-05-09 17:13:44 -07002508 cmd.extend(spec)
2509
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002510 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002511 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002512 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2513 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002514 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002515 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002516 ok = True
2517 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002518 # If needed, run the 'git remote prune' the first time through the loop
2519 elif (not _i and
2520 "error:" in gitcmd.stderr and
2521 "git remote prune" in gitcmd.stderr):
2522 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002523 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002524 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002525 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002526 break
2527 continue
Brian Harring14a66742012-09-28 20:21:57 -07002528 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002529 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2530 # in sha1 mode, we just tried sync'ing from the upstream field; it
2531 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002532 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002533 elif ret < 0:
2534 # Git died with a signal, exit immediately
2535 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002536 if not verbose:
2537 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002538 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002539
2540 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002541 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002542 if old_packed != '':
2543 _lwrite(packed_refs, old_packed)
2544 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002545 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002546 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002547
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002548 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002549 # We just synced the upstream given branch; verify we
2550 # got what we wanted, else trigger a second run of all
2551 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002552 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002553 # Sync the current branch only with depth set to None.
2554 # We always pass depth=None down to avoid infinite recursion.
2555 return self._RemoteFetch(
2556 name=name, quiet=quiet, verbose=verbose,
2557 current_branch_only=current_branch_only and depth,
2558 initial=False, alt_dir=alt_dir,
2559 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002560
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002561 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002562
Mike Frysingere50b6a72020-02-19 01:45:48 -05002563 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002564 if initial and \
2565 (self.manifest.manifestProject.config.GetString('repo.depth') or
2566 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002567 return False
2568
2569 remote = self.GetRemote(self.remote.name)
2570 bundle_url = remote.url + '/clone.bundle'
2571 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002572 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2573 'persistent-http',
2574 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002575 return False
2576
2577 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2578 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2579
2580 exist_dst = os.path.exists(bundle_dst)
2581 exist_tmp = os.path.exists(bundle_tmp)
2582
2583 if not initial and not exist_dst and not exist_tmp:
2584 return False
2585
2586 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002587 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2588 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002589 if not exist_dst:
2590 return False
2591
2592 cmd = ['fetch']
2593 if quiet:
2594 cmd.append('--quiet')
2595 if not self.worktree:
2596 cmd.append('--update-head-ok')
2597 cmd.append(bundle_dst)
2598 for f in remote.fetch:
2599 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002600 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002601
2602 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002603 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002604 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002605 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002606 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002607 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608
Mike Frysingere50b6a72020-02-19 01:45:48 -05002609 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002610 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002611 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002612
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002613 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002614 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002615 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002616 if os.path.exists(tmpPath):
2617 size = os.stat(tmpPath).st_size
2618 if size >= 1024:
2619 cmd += ['--continue-at', '%d' % (size,)]
2620 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002621 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002622 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002623 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002624 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002625 if proxy:
2626 cmd += ['--proxy', proxy]
2627 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2628 cmd += ['--proxy', os.environ['http_proxy']]
2629 if srcUrl.startswith('persistent-https'):
2630 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2631 elif srcUrl.startswith('persistent-http'):
2632 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002633 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002634
Dave Borowitz137d0132015-01-02 11:12:54 -08002635 if IsTrace():
2636 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002637 if verbose:
2638 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2639 stdout = None if verbose else subprocess.PIPE
2640 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002641 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002642 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002643 except OSError:
2644 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002645
Mike Frysingere50b6a72020-02-19 01:45:48 -05002646 (output, _) = proc.communicate()
2647 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002648
Dave Borowitz137d0132015-01-02 11:12:54 -08002649 if curlret == 22:
2650 # From curl man page:
2651 # 22: HTTP page not retrieved. The requested url was not found or
2652 # returned another error with the HTTP error code being 400 or above.
2653 # This return code only appears if -f, --fail is used.
2654 if not quiet:
2655 print("Server does not provide clone.bundle; ignoring.",
2656 file=sys.stderr)
2657 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002658 elif curlret and not verbose and output:
2659 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002660
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002661 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002662 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002663 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002664 return True
2665 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002666 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002667 return False
2668 else:
2669 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002670
Kris Giesingc8d882a2014-12-23 13:02:32 -08002671 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002672 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002673 with open(path, 'rb') as f:
2674 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002675 return True
2676 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002677 if not quiet:
2678 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002679 return False
2680 except OSError:
2681 return False
2682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002683 def _Checkout(self, rev, quiet=False):
2684 cmd = ['checkout']
2685 if quiet:
2686 cmd.append('-q')
2687 cmd.append(rev)
2688 cmd.append('--')
2689 if GitCommand(self, cmd).Wait() != 0:
2690 if self._allrefs:
2691 raise GitError('%s checkout %s ' % (self.name, rev))
2692
Anthony King7bdac712014-07-16 12:56:40 +01002693 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002694 cmd = ['cherry-pick']
2695 cmd.append(rev)
2696 cmd.append('--')
2697 if GitCommand(self, cmd).Wait() != 0:
2698 if self._allrefs:
2699 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2700
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302701 def _LsRemote(self, refs):
2702 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302703 p = GitCommand(self, cmd, capture_stdout=True)
2704 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002705 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302706 return None
2707
Anthony King7bdac712014-07-16 12:56:40 +01002708 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002709 cmd = ['revert']
2710 cmd.append('--no-edit')
2711 cmd.append(rev)
2712 cmd.append('--')
2713 if GitCommand(self, cmd).Wait() != 0:
2714 if self._allrefs:
2715 raise GitError('%s revert %s ' % (self.name, rev))
2716
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002717 def _ResetHard(self, rev, quiet=True):
2718 cmd = ['reset', '--hard']
2719 if quiet:
2720 cmd.append('-q')
2721 cmd.append(rev)
2722 if GitCommand(self, cmd).Wait() != 0:
2723 raise GitError('%s reset --hard %s ' % (self.name, rev))
2724
Martin Kellye4e94d22017-03-21 16:05:12 -07002725 def _SyncSubmodules(self, quiet=True):
2726 cmd = ['submodule', 'update', '--init', '--recursive']
2727 if quiet:
2728 cmd.append('-q')
2729 if GitCommand(self, cmd).Wait() != 0:
2730 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2731
Anthony King7bdac712014-07-16 12:56:40 +01002732 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002733 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002734 if onto is not None:
2735 cmd.extend(['--onto', onto])
2736 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002737 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738 raise GitError('%s rebase %s ' % (self.name, upstream))
2739
Pierre Tardy3d125942012-05-04 12:18:12 +02002740 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002741 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002742 if ffonly:
2743 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002744 if GitCommand(self, cmd).Wait() != 0:
2745 raise GitError('%s merge %s ' % (self.name, head))
2746
David Pursehousee8ace262020-02-13 12:41:15 +09002747 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002748 init_git_dir = not os.path.exists(self.gitdir)
2749 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002750 try:
2751 # Initialize the bare repository, which contains all of the objects.
2752 if init_obj_dir:
2753 os.makedirs(self.objdir)
2754 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002755
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002756 # Enable per-worktree config file support if possible. This is more a
2757 # nice-to-have feature for users rather than a hard requirement.
2758 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002759 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002760
Kevin Degib1a07b82015-07-27 13:33:43 -06002761 # If we have a separate directory to hold refs, initialize it as well.
2762 if self.objdir != self.gitdir:
2763 if init_git_dir:
2764 os.makedirs(self.gitdir)
2765
2766 if init_obj_dir or init_git_dir:
2767 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2768 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002769 try:
2770 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2771 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002772 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002773 print("Retrying clone after deleting %s" %
2774 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002775 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002776 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2777 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002778 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002779 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002780 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2781 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002782 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002783 raise e
2784 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002785
Kevin Degi384b3c52014-10-16 16:02:58 -06002786 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002787 mp = self.manifest.manifestProject
2788 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002789
Kevin Degib1a07b82015-07-27 13:33:43 -06002790 if ref_dir or mirror_git:
2791 if not mirror_git:
2792 mirror_git = os.path.join(ref_dir, self.name + '.git')
2793 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2794 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002795 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2796 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002797
Kevin Degib1a07b82015-07-27 13:33:43 -06002798 if os.path.exists(mirror_git):
2799 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002800 elif os.path.exists(repo_git):
2801 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002802 elif os.path.exists(worktrees_git):
2803 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002804 else:
2805 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002806
Kevin Degib1a07b82015-07-27 13:33:43 -06002807 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002808 if not os.path.isabs(ref_dir):
2809 # The alternate directory is relative to the object database.
2810 ref_dir = os.path.relpath(ref_dir,
2811 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002812 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2813 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002814
David Pursehousee8ace262020-02-13 12:41:15 +09002815 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002816
2817 m = self.manifest.manifestProject.config
2818 for key in ['user.name', 'user.email']:
2819 if m.Has(key, include_defaults=False):
2820 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002821 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002822 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002823 if self.manifest.IsMirror:
2824 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002825 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002826 self.config.SetString('core.bare', None)
2827 except Exception:
2828 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002829 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002830 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002831 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002832 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002833
David Pursehousee8ace262020-02-13 12:41:15 +09002834 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002835 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002836 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002837
David Pursehousee8ace262020-02-13 12:41:15 +09002838 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002839 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002840 if not os.path.exists(hooks):
2841 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002842 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002843 name = os.path.basename(stock_hook)
2844
Victor Boivie65e0f352011-04-18 11:23:29 +02002845 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002846 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002847 # Don't install a Gerrit Code Review hook if this
2848 # project does not appear to use it for reviews.
2849 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002850 # Since the manifest project is one of those, but also
2851 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002852 continue
2853
2854 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002855 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002856 continue
2857 if os.path.exists(dst):
2858 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002859 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002860 else:
David Pursehousee8ace262020-02-13 12:41:15 +09002861 if not quiet:
2862 _warn("%s: Not replacing locally modified %s hook",
2863 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002864 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002865 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002866 platform_utils.symlink(
2867 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002868 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002869 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002870 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002871 else:
2872 raise
2873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002874 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002875 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002876 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002877 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002878 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002879 remote.review = self.remote.review
2880 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002882 if self.worktree:
2883 remote.ResetFetch(mirror=False)
2884 else:
2885 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002886 remote.Save()
2887
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002888 def _InitMRef(self):
2889 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002890 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002891
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002892 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002893 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002894
2895 def _InitAnyMRef(self, ref):
2896 cur = self.bare_ref.symref(ref)
2897
2898 if self.revisionId:
2899 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2900 msg = 'manifest set to %s' % self.revisionId
2901 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002902 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002903 else:
2904 remote = self.GetRemote(self.remote.name)
2905 dst = remote.ToLocal(self.revisionExpr)
2906 if cur != dst:
2907 msg = 'manifest set to %s' % self.revisionExpr
2908 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002909
Kevin Degi384b3c52014-10-16 16:02:58 -06002910 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002911 # Git worktrees don't use symlinks to share at all.
2912 if self.use_git_worktrees:
2913 return
2914
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002915 symlink_files = self.shareable_files[:]
2916 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002917 if share_refs:
2918 symlink_files += self.working_tree_files
2919 symlink_dirs += self.working_tree_dirs
2920 to_symlink = symlink_files + symlink_dirs
2921 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002922 # Try to self-heal a bit in simple cases.
2923 dst_path = os.path.join(destdir, name)
2924 src_path = os.path.join(srcdir, name)
2925
2926 if name in self.working_tree_dirs:
2927 # If the dir is missing under .repo/projects/, create it.
2928 if not os.path.exists(src_path):
2929 os.makedirs(src_path)
2930
2931 elif name in self.working_tree_files:
2932 # If it's a file under the checkout .git/ and the .repo/projects/ has
2933 # nothing, move the file under the .repo/projects/ tree.
2934 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2935 platform_utils.rename(dst_path, src_path)
2936
2937 # If the path exists under the .repo/projects/ and there's no symlink
2938 # under the checkout .git/, recreate the symlink.
2939 if name in self.working_tree_dirs or name in self.working_tree_files:
2940 if os.path.exists(src_path) and not os.path.exists(dst_path):
2941 platform_utils.symlink(
2942 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2943
2944 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002945 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002946 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002947 # Fail if the links are pointing to the wrong place
2948 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002949 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002950 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002951 'work tree. If you\'re comfortable with the '
2952 'possibility of losing the work tree\'s git metadata,'
2953 ' use `repo sync --force-sync {0}` to '
2954 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002955
David James8d201162013-10-11 17:03:19 -07002956 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2957 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2958
2959 Args:
2960 gitdir: The bare git repository. Must already be initialized.
2961 dotgit: The repository you would like to initialize.
2962 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2963 Only one work tree can store refs under a given |gitdir|.
2964 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2965 This saves you the effort of initializing |dotgit| yourself.
2966 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002967 symlink_files = self.shareable_files[:]
2968 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002969 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002970 symlink_files += self.working_tree_files
2971 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002972 to_symlink = symlink_files + symlink_dirs
2973
2974 to_copy = []
2975 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002976 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002977
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002978 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002979 for name in set(to_copy).union(to_symlink):
2980 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002981 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002982 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002983
Kevin Degi384b3c52014-10-16 16:02:58 -06002984 if os.path.lexists(dst):
2985 continue
David James8d201162013-10-11 17:03:19 -07002986
2987 # If the source dir doesn't exist, create an empty dir.
2988 if name in symlink_dirs and not os.path.lexists(src):
2989 os.makedirs(src)
2990
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002991 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002992 platform_utils.symlink(
2993 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002994 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002995 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002996 shutil.copytree(src, dst)
2997 elif os.path.isfile(src):
2998 shutil.copy(src, dst)
2999
Conley Owens80b87fe2014-05-09 17:13:44 -07003000 # If the source file doesn't exist, ensure the destination
3001 # file doesn't either.
3002 if name in symlink_files and not os.path.lexists(src):
3003 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003004 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003005 except OSError:
3006 pass
3007
David James8d201162013-10-11 17:03:19 -07003008 except OSError as e:
3009 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003010 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003011 else:
3012 raise
3013
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003014 def _InitGitWorktree(self):
3015 """Init the project using git worktrees."""
3016 self.bare_git.worktree('prune')
3017 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3018 self.worktree, self.GetRevisionId())
3019
3020 # Rewrite the internal state files to use relative paths between the
3021 # checkouts & worktrees.
3022 dotgit = os.path.join(self.worktree, '.git')
3023 with open(dotgit, 'r') as fp:
3024 # Figure out the checkout->worktree path.
3025 setting = fp.read()
3026 assert setting.startswith('gitdir:')
3027 git_worktree_path = setting.split(':', 1)[1].strip()
3028 # Use relative path from checkout->worktree.
3029 with open(dotgit, 'w') as fp:
3030 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3031 file=fp)
3032 # Use relative path from worktree->checkout.
3033 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3034 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3035
Martin Kellye4e94d22017-03-21 16:05:12 -07003036 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003037 realdotgit = os.path.join(self.worktree, '.git')
3038 tmpdotgit = realdotgit + '.tmp'
3039 init_dotgit = not os.path.exists(realdotgit)
3040 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003041 if self.use_git_worktrees:
3042 self._InitGitWorktree()
3043 self._CopyAndLinkFiles()
3044 return
3045
Mike Frysingerf4545122019-11-11 04:34:16 -05003046 dotgit = tmpdotgit
3047 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3048 os.makedirs(tmpdotgit)
3049 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3050 copy_all=False)
3051 else:
3052 dotgit = realdotgit
3053
Kevin Degib1a07b82015-07-27 13:33:43 -06003054 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003055 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3056 except GitError as e:
3057 if force_sync and not init_dotgit:
3058 try:
3059 platform_utils.rmtree(dotgit)
3060 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003061 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003062 raise e
3063 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003064
Mike Frysingerf4545122019-11-11 04:34:16 -05003065 if init_dotgit:
3066 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003067
Mike Frysingerf4545122019-11-11 04:34:16 -05003068 # Now that the .git dir is fully set up, move it to its final home.
3069 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003070
Mike Frysingerf4545122019-11-11 04:34:16 -05003071 # Finish checking out the worktree.
3072 cmd = ['read-tree', '--reset', '-u']
3073 cmd.append('-v')
3074 cmd.append(HEAD)
3075 if GitCommand(self, cmd).Wait() != 0:
3076 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003077
Mike Frysingerf4545122019-11-11 04:34:16 -05003078 if submodules:
3079 self._SyncSubmodules(quiet=True)
3080 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003081
Renaud Paquay788e9622017-01-27 11:41:12 -08003082 def _get_symlink_error_message(self):
3083 if platform_utils.isWindows():
3084 return ('Unable to create symbolic link. Please re-run the command as '
3085 'Administrator, or see '
3086 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3087 'for other options.')
3088 return 'filesystem must support symlinks'
3089
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003090 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003091 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003092
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003093 def _revlist(self, *args, **kw):
3094 a = []
3095 a.extend(args)
3096 a.append('--')
3097 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003098
3099 @property
3100 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003101 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003102
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003103 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003104 """Get logs between two revisions of this project."""
3105 comp = '..'
3106 if rev1:
3107 revs = [rev1]
3108 if rev2:
3109 revs.extend([comp, rev2])
3110 cmd = ['log', ''.join(revs)]
3111 out = DiffColoring(self.config)
3112 if out.is_on and color:
3113 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003114 if pretty_format is not None:
3115 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003116 if oneline:
3117 cmd.append('--oneline')
3118
3119 try:
3120 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3121 if log.Wait() == 0:
3122 return log.stdout
3123 except GitError:
3124 # worktree may not exist if groups changed for example. In that case,
3125 # try in gitdir instead.
3126 if not os.path.exists(self.worktree):
3127 return self.bare_git.log(*cmd[1:])
3128 else:
3129 raise
3130 return None
3131
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003132 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3133 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003134 """Get the list of logs from this revision to given revisionId"""
3135 logs = {}
3136 selfId = self.GetRevisionId(self._allrefs)
3137 toId = toProject.GetRevisionId(toProject._allrefs)
3138
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003139 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3140 pretty_format=pretty_format)
3141 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3142 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003143 return logs
3144
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003145 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
David James8d201162013-10-11 17:03:19 -07003147 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003148 self._project = project
3149 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003150 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003152 def LsOthers(self):
3153 p = GitCommand(self._project,
3154 ['ls-files',
3155 '-z',
3156 '--others',
3157 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003158 bare=False,
David James8d201162013-10-11 17:03:19 -07003159 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003160 capture_stdout=True,
3161 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003162 if p.Wait() == 0:
3163 out = p.stdout
3164 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003165 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003166 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003167 return []
3168
3169 def DiffZ(self, name, *args):
3170 cmd = [name]
3171 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003172 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003173 cmd.extend(args)
3174 p = GitCommand(self._project,
3175 cmd,
David James8d201162013-10-11 17:03:19 -07003176 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003177 bare=False,
3178 capture_stdout=True,
3179 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003180 try:
3181 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003182 if not hasattr(out, 'encode'):
3183 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003184 r = {}
3185 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003186 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003187 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003188 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003189 info = next(out)
3190 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003191 except StopIteration:
3192 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003193
3194 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003195
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003196 def __init__(self, path, omode, nmode, oid, nid, state):
3197 self.path = path
3198 self.src_path = None
3199 self.old_mode = omode
3200 self.new_mode = nmode
3201 self.old_id = oid
3202 self.new_id = nid
3203
3204 if len(state) == 1:
3205 self.status = state
3206 self.level = None
3207 else:
3208 self.status = state[:1]
3209 self.level = state[1:]
3210 while self.level.startswith('0'):
3211 self.level = self.level[1:]
3212
3213 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003214 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003215 if info.status in ('R', 'C'):
3216 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003217 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003218 r[info.path] = info
3219 return r
3220 finally:
3221 p.Wait()
3222
3223 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003224 if self._bare:
3225 path = os.path.join(self._project.gitdir, HEAD)
3226 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003227 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003228 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003229 with open(path) as fd:
3230 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003231 except IOError as e:
3232 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003233 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303234 line = line.decode()
3235 except AttributeError:
3236 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003237 if line.startswith('ref: '):
3238 return line[5:-1]
3239 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003240
3241 def SetHead(self, ref, message=None):
3242 cmdv = []
3243 if message is not None:
3244 cmdv.extend(['-m', message])
3245 cmdv.append(HEAD)
3246 cmdv.append(ref)
3247 self.symbolic_ref(*cmdv)
3248
3249 def DetachHead(self, new, message=None):
3250 cmdv = ['--no-deref']
3251 if message is not None:
3252 cmdv.extend(['-m', message])
3253 cmdv.append(HEAD)
3254 cmdv.append(new)
3255 self.update_ref(*cmdv)
3256
3257 def UpdateRef(self, name, new, old=None,
3258 message=None,
3259 detach=False):
3260 cmdv = []
3261 if message is not None:
3262 cmdv.extend(['-m', message])
3263 if detach:
3264 cmdv.append('--no-deref')
3265 cmdv.append(name)
3266 cmdv.append(new)
3267 if old is not None:
3268 cmdv.append(old)
3269 self.update_ref(*cmdv)
3270
3271 def DeleteRef(self, name, old=None):
3272 if not old:
3273 old = self.rev_parse(name)
3274 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003275 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003276
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003277 def rev_list(self, *args, **kw):
3278 if 'format' in kw:
3279 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3280 else:
3281 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003282 cmdv.extend(args)
3283 p = GitCommand(self._project,
3284 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003285 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003286 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003287 capture_stdout=True,
3288 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003289 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003290 raise GitError('%s rev-list %s: %s' %
3291 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003292 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003293
3294 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003295 """Allow arbitrary git commands using pythonic syntax.
3296
3297 This allows you to do things like:
3298 git_obj.rev_parse('HEAD')
3299
3300 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3301 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003302 Any other positional arguments will be passed to the git command, and the
3303 following keyword arguments are supported:
3304 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003305
3306 Args:
3307 name: The name of the git command to call. Any '_' characters will
3308 be replaced with '-'.
3309
3310 Returns:
3311 A callable object that will try to call git with the named command.
3312 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003313 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003314
Dave Borowitz091f8932012-10-23 17:01:04 -07003315 def runner(*args, **kwargs):
3316 cmdv = []
3317 config = kwargs.pop('config', None)
3318 for k in kwargs:
3319 raise TypeError('%s() got an unexpected keyword argument %r'
3320 % (name, k))
3321 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303322 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003323 cmdv.append('-c')
3324 cmdv.append('%s=%s' % (k, v))
3325 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003326 cmdv.extend(args)
3327 p = GitCommand(self._project,
3328 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003329 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003330 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003331 capture_stdout=True,
3332 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003333 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003334 raise GitError('%s %s: %s' %
3335 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003336 r = p.stdout
3337 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3338 return r[:-1]
3339 return r
3340 return runner
3341
3342
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003343class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003344
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003345 def __str__(self):
3346 return 'prior sync failed; rebase still in progress'
3347
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003348
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003349class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003350
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003351 def __str__(self):
3352 return 'contains uncommitted changes'
3353
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003354
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003355class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003356
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003357 def __init__(self, project, text):
3358 self.project = project
3359 self.text = text
3360
3361 def Print(self, syncbuf):
3362 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3363 syncbuf.out.nl()
3364
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003365
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003366class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003367
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003368 def __init__(self, project, why):
3369 self.project = project
3370 self.why = why
3371
3372 def Print(self, syncbuf):
3373 syncbuf.out.fail('error: %s/: %s',
3374 self.project.relpath,
3375 str(self.why))
3376 syncbuf.out.nl()
3377
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003378
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003379class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003380
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003381 def __init__(self, project, action):
3382 self.project = project
3383 self.action = action
3384
3385 def Run(self, syncbuf):
3386 out = syncbuf.out
3387 out.project('project %s/', self.project.relpath)
3388 out.nl()
3389 try:
3390 self.action()
3391 out.nl()
3392 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003393 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003394 out.nl()
3395 return False
3396
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003397
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003398class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003399
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003400 def __init__(self, config):
3401 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003402 self.project = self.printer('header', attr='bold')
3403 self.info = self.printer('info')
3404 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003405
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003406
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003407class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003408
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003409 def __init__(self, config, detach_head=False):
3410 self._messages = []
3411 self._failures = []
3412 self._later_queue1 = []
3413 self._later_queue2 = []
3414
3415 self.out = _SyncColoring(config)
3416 self.out.redirect(sys.stderr)
3417
3418 self.detach_head = detach_head
3419 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003420 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003421
3422 def info(self, project, fmt, *args):
3423 self._messages.append(_InfoMessage(project, fmt % args))
3424
3425 def fail(self, project, err=None):
3426 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003427 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003428
3429 def later1(self, project, what):
3430 self._later_queue1.append(_Later(project, what))
3431
3432 def later2(self, project, what):
3433 self._later_queue2.append(_Later(project, what))
3434
3435 def Finish(self):
3436 self._PrintMessages()
3437 self._RunLater()
3438 self._PrintMessages()
3439 return self.clean
3440
David Rileye0684ad2017-04-05 00:02:59 -07003441 def Recently(self):
3442 recent_clean = self.recent_clean
3443 self.recent_clean = True
3444 return recent_clean
3445
3446 def _MarkUnclean(self):
3447 self.clean = False
3448 self.recent_clean = False
3449
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003450 def _RunLater(self):
3451 for q in ['_later_queue1', '_later_queue2']:
3452 if not self._RunQueue(q):
3453 return
3454
3455 def _RunQueue(self, queue):
3456 for m in getattr(self, queue):
3457 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003458 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003459 return False
3460 setattr(self, queue, [])
3461 return True
3462
3463 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003464 if self._messages or self._failures:
3465 if os.isatty(2):
3466 self.out.write(progress.CSI_ERASE_LINE)
3467 self.out.write('\r')
3468
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003469 for m in self._messages:
3470 m.Print(self)
3471 for m in self._failures:
3472 m.Print(self)
3473
3474 self._messages = []
3475 self._failures = []
3476
3477
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003478class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003479
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003480 """A special project housed under .repo.
3481 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003482
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003483 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003484 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003485 manifest=manifest,
3486 name=name,
3487 gitdir=gitdir,
3488 objdir=gitdir,
3489 worktree=worktree,
3490 remote=RemoteSpec('origin'),
3491 relpath='.repo/%s' % name,
3492 revisionExpr='refs/heads/master',
3493 revisionId=None,
3494 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003495
3496 def PreSync(self):
3497 if self.Exists:
3498 cb = self.CurrentBranch
3499 if cb:
3500 base = self.GetBranch(cb).merge
3501 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003502 self.revisionExpr = base
3503 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003504
Martin Kelly224a31a2017-07-10 14:46:25 -07003505 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003506 """ Prepare MetaProject for manifest branch switch
3507 """
3508
3509 # detach and delete manifest branch, allowing a new
3510 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003511 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003512 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003513 syncbuf.Finish()
3514
3515 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003516 ['update-ref', '-d', 'refs/heads/default'],
3517 capture_stdout=True,
3518 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003519
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003520 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003521 def LastFetch(self):
3522 try:
3523 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3524 return os.path.getmtime(fh)
3525 except OSError:
3526 return 0
3527
3528 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003529 def HasChanges(self):
3530 """Has the remote received new commits not yet checked out?
3531 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003532 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003533 return False
3534
David Pursehouse8a68ff92012-09-24 12:15:13 +09003535 all_refs = self.bare_ref.all
3536 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003537 head = self.work_git.GetHead()
3538 if head.startswith(R_HEADS):
3539 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003540 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003541 except KeyError:
3542 head = None
3543
3544 if revid == head:
3545 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003546 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003547 return True
3548 return False