blob: 3d22998e9a514464ae8e81b80a082488ad41a3f5 [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
Shawn O. Pearce559b8462009-03-02 12:56:08 -080039from error import ManifestInvalidRevisionError
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
Chirayu Desai217ea7d2013-03-01 19:14:38 +053055 input = raw_input
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
Jonathan Nieder93719792015-03-17 11:29:58 -070088_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070089
90
Jonathan Nieder93719792015-03-17 11:29:58 -070091def _ProjectHooks():
92 """List the hooks present in the 'hooks' directory.
93
94 These hooks are project hooks and are copied to the '.git/hooks' directory
95 of all subprojects.
96
97 This function caches the list of hooks (based on the contents of the
98 'repo/hooks' directory) on the first call.
99
100 Returns:
101 A list of absolute paths to all of the files in the hooks directory.
102 """
103 global _project_hook_list
104 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700105 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700106 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700107 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700108 return _project_hook_list
109
110
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700111class DownloadedChange(object):
112 _commit_cache = None
113
114 def __init__(self, project, base, change_id, ps_id, commit):
115 self.project = project
116 self.base = base
117 self.change_id = change_id
118 self.ps_id = ps_id
119 self.commit = commit
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700124 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700150 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
151 '--abbrev-commit',
152 '--pretty=oneline',
153 '--reverse',
154 '--date-order',
155 not_rev(self.base),
156 R_HEADS + self.name,
157 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700176 def UploadForReview(self, people,
177 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000178 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200179 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700180 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200181 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200182 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800183 validate_certs=True,
184 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800185 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700186 people,
Brian Harring435370c2012-07-28 15:37:04 -0700187 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000188 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200189 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700190 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200191 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200192 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800193 validate_certs=validate_certs,
194 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700196 def GetPublishedRefs(self):
197 refs = {}
198 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700199 self.branch.remote.SshReviewUrl(self.project.UserEmail),
200 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700201 for line in output.split('\n'):
202 try:
203 (sha, ref) = line.split()
204 refs[sha] = ref
205 except ValueError:
206 pass
207
208 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700210
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 def __init__(self, config):
214 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100215 self.project = self.printer('header', attr='bold')
216 self.branch = self.printer('header', attr='bold')
217 self.nobranch = self.printer('nobranch', fg='red')
218 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219
Anthony King7bdac712014-07-16 12:56:40 +0100220 self.added = self.printer('added', fg='green')
221 self.changed = self.printer('changed', fg='red')
222 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
224
225class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700226
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 def __init__(self, config):
228 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100229 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400230 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700232
Anthony King7bdac712014-07-16 12:56:40 +0100233class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
James W. Mills24c13082012-04-12 15:04:13 -0500235 def __init__(self, name, value, keep):
236 self.name = name
237 self.value = value
238 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
Anthony King7bdac712014-07-16 12:56:40 +0100241class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700242
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800243 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244 self.src = src
245 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800246 self.abs_src = abssrc
247 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248
249 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800250 src = self.abs_src
251 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 # copy file if it does not exist or is out of date
253 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
254 try:
255 # remove existing file first, since it might be read-only
256 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800257 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400258 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200259 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700260 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200261 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262 shutil.copy(src, dest)
263 # make the file read-only
264 mode = os.stat(dest)[stat.ST_MODE]
265 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
266 os.chmod(dest, mode)
267 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700268 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700270
Anthony King7bdac712014-07-16 12:56:40 +0100271class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700272
Wink Saville4c426ef2015-06-03 08:05:17 -0700273 def __init__(self, git_worktree, src, dest, relsrc, absdest):
274 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500275 self.src = src
276 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700277 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500278 self.abs_dest = absdest
279
Wink Saville4c426ef2015-06-03 08:05:17 -0700280 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700282 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500283 try:
284 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800285 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800286 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500287 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700288 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700289 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500290 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700291 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500292 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700293 _error('Cannot link file %s to %s', relSrc, absDest)
294
295 def _Link(self):
296 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
297 on the src linking all of the files in the source in to the destination
298 directory.
299 """
300 # We use the absSrc to handle the situation where the current directory
301 # is not the root of the repo
302 absSrc = os.path.join(self.git_worktree, self.src)
303 if os.path.exists(absSrc):
304 # Entity exists so just a simple one to one link operation
305 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
306 else:
307 # Entity doesn't exist assume there is a wild card
308 absDestDir = self.abs_dest
Renaud Paquaybed8b622018-09-27 10:46:58 -0700309 if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir):
Wink Saville4c426ef2015-06-03 08:05:17 -0700310 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700311 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700312 else:
313 absSrcFiles = glob.glob(absSrc)
314 for absSrcFile in absSrcFiles:
315 # Create a releative path from source dir to destination dir
316 absSrcDir = os.path.dirname(absSrcFile)
317 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
318
319 # Get the source file name
320 srcFile = os.path.basename(absSrcFile)
321
322 # Now form the final full paths to srcFile. They will be
323 # absolute for the desintaiton and relative for the srouce.
324 absDest = os.path.join(absDestDir, srcFile)
325 relSrc = os.path.join(relSrcDir, srcFile)
326 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500327
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700328
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700329class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700330
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700331 def __init__(self,
332 name,
Anthony King7bdac712014-07-16 12:56:40 +0100333 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700334 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100335 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700336 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700337 orig_name=None,
338 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700339 self.name = name
340 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700341 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700342 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100343 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700344 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700345 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700346
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700347
Doug Anderson37282b42011-03-04 11:54:18 -0800348class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Doug Anderson37282b42011-03-04 11:54:18 -0800350 """A RepoHook contains information about a script to run as a hook.
351
352 Hooks are used to run a python script before running an upload (for instance,
353 to run presubmit checks). Eventually, we may have hooks for other actions.
354
355 This shouldn't be confused with files in the 'repo/hooks' directory. Those
356 files are copied into each '.git/hooks' folder for each project. Repo-level
357 hooks are associated instead with repo actions.
358
359 Hooks are always python. When a hook is run, we will load the hook into the
360 interpreter and execute its main() function.
361 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700362
Doug Anderson37282b42011-03-04 11:54:18 -0800363 def __init__(self,
364 hook_type,
365 hooks_project,
366 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400367 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800368 abort_if_user_denies=False):
369 """RepoHook constructor.
370
371 Params:
372 hook_type: A string representing the type of hook. This is also used
373 to figure out the name of the file containing the hook. For
374 example: 'pre-upload'.
375 hooks_project: The project containing the repo hooks. If you have a
376 manifest, this is manifest.repo_hooks_project. OK if this is None,
377 which will make the hook a no-op.
378 topdir: Repo's top directory (the one containing the .repo directory).
379 Scripts will run with CWD as this directory. If you have a manifest,
380 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400381 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800382 abort_if_user_denies: If True, we'll throw a HookError() if the user
383 doesn't allow us to run the hook.
384 """
385 self._hook_type = hook_type
386 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400387 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800388 self._topdir = topdir
389 self._abort_if_user_denies = abort_if_user_denies
390
391 # Store the full path to the script for convenience.
392 if self._hooks_project:
393 self._script_fullpath = os.path.join(self._hooks_project.worktree,
394 self._hook_type + '.py')
395 else:
396 self._script_fullpath = None
397
398 def _GetHash(self):
399 """Return a hash of the contents of the hooks directory.
400
401 We'll just use git to do this. This hash has the property that if anything
402 changes in the directory we will return a different has.
403
404 SECURITY CONSIDERATION:
405 This hash only represents the contents of files in the hook directory, not
406 any other files imported or called by hooks. Changes to imported files
407 can change the script behavior without affecting the hash.
408
409 Returns:
410 A string representing the hash. This will always be ASCII so that it can
411 be printed to the user easily.
412 """
413 assert self._hooks_project, "Must have hooks to calculate their hash."
414
415 # We will use the work_git object rather than just calling GetRevisionId().
416 # That gives us a hash of the latest checked in version of the files that
417 # the user will actually be executing. Specifically, GetRevisionId()
418 # doesn't appear to change even if a user checks out a different version
419 # of the hooks repo (via git checkout) nor if a user commits their own revs.
420 #
421 # NOTE: Local (non-committed) changes will not be factored into this hash.
422 # I think this is OK, since we're really only worried about warning the user
423 # about upstream changes.
424 return self._hooks_project.work_git.rev_parse('HEAD')
425
426 def _GetMustVerb(self):
427 """Return 'must' if the hook is required; 'should' if not."""
428 if self._abort_if_user_denies:
429 return 'must'
430 else:
431 return 'should'
432
433 def _CheckForHookApproval(self):
434 """Check to see whether this hook has been approved.
435
Mike Frysinger40252c22016-08-15 21:23:44 -0400436 We'll accept approval of manifest URLs if they're using secure transports.
437 This way the user can say they trust the manifest hoster. For insecure
438 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800439
440 Note that we ask permission for each individual hook even though we use
441 the hash of all hooks when detecting changes. We'd like the user to be
442 able to approve / deny each hook individually. We only use the hash of all
443 hooks because there is no other easy way to detect changes to local imports.
444
445 Returns:
446 True if this hook is approved to run; False otherwise.
447
448 Raises:
449 HookError: Raised if the user doesn't approve and abort_if_user_denies
450 was passed to the consturctor.
451 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400452 if self._ManifestUrlHasSecureScheme():
453 return self._CheckForHookApprovalManifest()
454 else:
455 return self._CheckForHookApprovalHash()
456
457 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
458 changed_prompt):
459 """Check for approval for a particular attribute and hook.
460
461 Args:
462 subkey: The git config key under [repo.hooks.<hook_type>] to store the
463 last approved string.
464 new_val: The new value to compare against the last approved one.
465 main_prompt: Message to display to the user to ask for approval.
466 changed_prompt: Message explaining why we're re-asking for approval.
467
468 Returns:
469 True if this hook is approved to run; False otherwise.
470
471 Raises:
472 HookError: Raised if the user doesn't approve and abort_if_user_denies
473 was passed to the consturctor.
474 """
Doug Anderson37282b42011-03-04 11:54:18 -0800475 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400476 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800477
Mike Frysinger40252c22016-08-15 21:23:44 -0400478 # Get the last value that the user approved for this hook; may be None.
479 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800480
Mike Frysinger40252c22016-08-15 21:23:44 -0400481 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800482 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800484 # Approval matched. We're done.
485 return True
486 else:
487 # Give the user a reason why we're prompting, since they last told
488 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400489 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800490 else:
491 prompt = ''
492
493 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
494 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400495 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530496 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900497 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800498
499 # User is doing a one-time approval.
500 if response in ('y', 'yes'):
501 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400502 elif response == 'always':
503 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800504 return True
505
506 # For anything else, we'll assume no approval.
507 if self._abort_if_user_denies:
508 raise HookError('You must allow the %s hook or use --no-verify.' %
509 self._hook_type)
510
511 return False
512
Mike Frysinger40252c22016-08-15 21:23:44 -0400513 def _ManifestUrlHasSecureScheme(self):
514 """Check if the URI for the manifest is a secure transport."""
515 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
516 parse_results = urllib.parse.urlparse(self._manifest_url)
517 return parse_results.scheme in secure_schemes
518
519 def _CheckForHookApprovalManifest(self):
520 """Check whether the user has approved this manifest host.
521
522 Returns:
523 True if this hook is approved to run; False otherwise.
524 """
525 return self._CheckForHookApprovalHelper(
526 'approvedmanifest',
527 self._manifest_url,
528 'Run hook scripts from %s' % (self._manifest_url,),
529 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
530
531 def _CheckForHookApprovalHash(self):
532 """Check whether the user has approved the hooks repo.
533
534 Returns:
535 True if this hook is approved to run; False otherwise.
536 """
537 prompt = ('Repo %s run the script:\n'
538 ' %s\n'
539 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700540 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400541 return self._CheckForHookApprovalHelper(
542 'approvedhash',
543 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700544 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400545 'Scripts have changed since %s was allowed.' % (self._hook_type,))
546
Mike Frysingerf7c51602019-06-18 17:23:39 -0400547 @staticmethod
548 def _ExtractInterpFromShebang(data):
549 """Extract the interpreter used in the shebang.
550
551 Try to locate the interpreter the script is using (ignoring `env`).
552
553 Args:
554 data: The file content of the script.
555
556 Returns:
557 The basename of the main script interpreter, or None if a shebang is not
558 used or could not be parsed out.
559 """
560 firstline = data.splitlines()[:1]
561 if not firstline:
562 return None
563
564 # The format here can be tricky.
565 shebang = firstline[0].strip()
566 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
567 if not m:
568 return None
569
570 # If the using `env`, find the target program.
571 interp = m.group(1)
572 if os.path.basename(interp) == 'env':
573 interp = m.group(2)
574
575 return interp
576
577 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
578 """Execute the hook script through |interp|.
579
580 Note: Support for this feature should be dropped ~Jun 2021.
581
582 Args:
583 interp: The Python program to run.
584 context: Basic Python context to execute the hook inside.
585 kwargs: Arbitrary arguments to pass to the hook script.
586
587 Raises:
588 HookError: When the hooks failed for any reason.
589 """
590 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
591 script = """
592import json, os, sys
593path = '''%(path)s'''
594kwargs = json.loads('''%(kwargs)s''')
595context = json.loads('''%(context)s''')
596sys.path.insert(0, os.path.dirname(path))
597data = open(path).read()
598exec(compile(data, path, 'exec'), context)
599context['main'](**kwargs)
600""" % {
601 'path': self._script_fullpath,
602 'kwargs': json.dumps(kwargs),
603 'context': json.dumps(context),
604 }
605
606 # We pass the script via stdin to avoid OS argv limits. It also makes
607 # unhandled exception tracebacks less verbose/confusing for users.
608 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
609 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
610 proc.communicate(input=script.encode('utf-8'))
611 if proc.returncode:
612 raise HookError('Failed to run %s hook.' % (self._hook_type,))
613
614 def _ExecuteHookViaImport(self, data, context, **kwargs):
615 """Execute the hook code in |data| directly.
616
617 Args:
618 data: The code of the hook to execute.
619 context: Basic Python context to execute the hook inside.
620 kwargs: Arbitrary arguments to pass to the hook script.
621
622 Raises:
623 HookError: When the hooks failed for any reason.
624 """
625 # Exec, storing global context in the context dict. We catch exceptions
626 # and convert to a HookError w/ just the failing traceback.
627 try:
628 exec(compile(data, self._script_fullpath, 'exec'), context)
629 except Exception:
630 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
631 (traceback.format_exc(), self._hook_type))
632
633 # Running the script should have defined a main() function.
634 if 'main' not in context:
635 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
636
637 # Call the main function in the hook. If the hook should cause the
638 # build to fail, it will raise an Exception. We'll catch that convert
639 # to a HookError w/ just the failing traceback.
640 try:
641 context['main'](**kwargs)
642 except Exception:
643 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
644 'above.' % (traceback.format_exc(), self._hook_type))
645
Doug Anderson37282b42011-03-04 11:54:18 -0800646 def _ExecuteHook(self, **kwargs):
647 """Actually execute the given hook.
648
649 This will run the hook's 'main' function in our python interpreter.
650
651 Args:
652 kwargs: Keyword arguments to pass to the hook. These are often specific
653 to the hook type. For instance, pre-upload hooks will contain
654 a project_list.
655 """
656 # Keep sys.path and CWD stashed away so that we can always restore them
657 # upon function exit.
658 orig_path = os.getcwd()
659 orig_syspath = sys.path
660
661 try:
662 # Always run hooks with CWD as topdir.
663 os.chdir(self._topdir)
664
665 # Put the hook dir as the first item of sys.path so hooks can do
666 # relative imports. We want to replace the repo dir as [0] so
667 # hooks can't import repo files.
668 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
669
Mike Frysingerf7c51602019-06-18 17:23:39 -0400670 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500671 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800672
Doug Anderson37282b42011-03-04 11:54:18 -0800673 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
674 # We don't actually want hooks to define their main with this argument--
675 # it's there to remind them that their hook should always take **kwargs.
676 # For instance, a pre-upload hook should be defined like:
677 # def main(project_list, **kwargs):
678 #
679 # This allows us to later expand the API without breaking old hooks.
680 kwargs = kwargs.copy()
681 kwargs['hook_should_take_kwargs'] = True
682
Mike Frysingerf7c51602019-06-18 17:23:39 -0400683 # See what version of python the hook has been written against.
684 data = open(self._script_fullpath).read()
685 interp = self._ExtractInterpFromShebang(data)
686 reexec = False
687 if interp:
688 prog = os.path.basename(interp)
689 if prog.startswith('python2') and sys.version_info.major != 2:
690 reexec = True
691 elif prog.startswith('python3') and sys.version_info.major == 2:
692 reexec = True
693
694 # Attempt to execute the hooks through the requested version of Python.
695 if reexec:
696 try:
697 self._ExecuteHookViaReexec(interp, context, **kwargs)
698 except OSError as e:
699 if e.errno == errno.ENOENT:
700 # We couldn't find the interpreter, so fallback to importing.
701 reexec = False
702 else:
703 raise
704
705 # Run the hook by importing directly.
706 if not reexec:
707 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800708 finally:
709 # Restore sys.path and CWD.
710 sys.path = orig_syspath
711 os.chdir(orig_path)
712
713 def Run(self, user_allows_all_hooks, **kwargs):
714 """Run the hook.
715
716 If the hook doesn't exist (because there is no hooks project or because
717 this particular hook is not enabled), this is a no-op.
718
719 Args:
720 user_allows_all_hooks: If True, we will never prompt about running the
721 hook--we'll just assume it's OK to run it.
722 kwargs: Keyword arguments to pass to the hook. These are often specific
723 to the hook type. For instance, pre-upload hooks will contain
724 a project_list.
725
726 Raises:
727 HookError: If there was a problem finding the hook or the user declined
728 to run a required hook (from _CheckForHookApproval).
729 """
730 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700731 if ((not self._hooks_project) or (self._hook_type not in
732 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800733 return
734
735 # Bail with a nice error if we can't find the hook.
736 if not os.path.isfile(self._script_fullpath):
737 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
738
739 # Make sure the user is OK with running the hook.
740 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
741 return
742
743 # Run the hook with the same version of python we're using.
744 self._ExecuteHook(**kwargs)
745
746
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600748 # These objects can be shared between several working trees.
749 shareable_files = ['description', 'info']
750 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
751 # These objects can only be used by a single working tree.
752 working_tree_files = ['config', 'packed-refs', 'shallow']
753 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 def __init__(self,
756 manifest,
757 name,
758 remote,
759 gitdir,
David James8d201162013-10-11 17:03:19 -0700760 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761 worktree,
762 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700763 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800764 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100765 rebase=True,
766 groups=None,
767 sync_c=False,
768 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900769 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100770 clone_depth=None,
771 upstream=None,
772 parent=None,
773 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900774 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700775 optimized_fetch=False,
776 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800777 """Init a Project object.
778
779 Args:
780 manifest: The XmlManifest object.
781 name: The `name` attribute of manifest.xml's project element.
782 remote: RemoteSpec object specifying its remote's properties.
783 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700784 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800785 worktree: Absolute path of git working tree.
786 relpath: Relative path of git working tree to repo's top directory.
787 revisionExpr: The `revision` attribute of manifest.xml's project element.
788 revisionId: git commit id for checking out.
789 rebase: The `rebase` attribute of manifest.xml's project element.
790 groups: The `groups` attribute of manifest.xml's project element.
791 sync_c: The `sync-c` attribute of manifest.xml's project element.
792 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900793 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800794 upstream: The `upstream` attribute of manifest.xml's project element.
795 parent: The parent Project object.
796 is_derived: False if the project was explicitly defined in the manifest;
797 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400798 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900799 optimized_fetch: If True, when a project is set to a sha1 revision, only
800 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700801 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800802 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 self.manifest = manifest
804 self.name = name
805 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800806 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700807 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800808 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700809 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800810 else:
811 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700813 self.revisionExpr = revisionExpr
814
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700815 if revisionId is None \
816 and revisionExpr \
817 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700818 self.revisionId = revisionExpr
819 else:
820 self.revisionId = revisionId
821
Mike Pontillod3153822012-02-28 11:53:24 -0800822 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700823 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700824 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800825 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900826 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900827 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700828 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800829 self.parent = parent
830 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900831 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800832 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800833
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500836 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500837 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700838 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
839 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800841 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700842 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800843 else:
844 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700845 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700846 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700847 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400848 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700849 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850
Doug Anderson37282b42011-03-04 11:54:18 -0800851 # This will be filled in if a project is later identified to be the
852 # project containing repo hooks.
853 self.enabled_repo_hooks = []
854
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800856 def Derived(self):
857 return self.is_derived
858
859 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700861 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
863 @property
864 def CurrentBranch(self):
865 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400866
867 The branch name omits the 'refs/heads/' prefix.
868 None is returned if the project is on a detached HEAD, or if the work_git is
869 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400871 try:
872 b = self.work_git.GetHead()
873 except NoManifestException:
874 # If the local checkout is in a bad state, don't barf. Let the callers
875 # process this like the head is unreadable.
876 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 if b.startswith(R_HEADS):
878 return b[len(R_HEADS):]
879 return None
880
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700881 def IsRebaseInProgress(self):
882 w = self.worktree
883 g = os.path.join(w, '.git')
884 return os.path.exists(os.path.join(g, 'rebase-apply')) \
885 or os.path.exists(os.path.join(g, 'rebase-merge')) \
886 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200887
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 def IsDirty(self, consider_untracked=True):
889 """Is the working directory modified in some way?
890 """
891 self.work_git.update_index('-q',
892 '--unmerged',
893 '--ignore-missing',
894 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900895 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 return True
897 if self.work_git.DiffZ('diff-files'):
898 return True
899 if consider_untracked and self.work_git.LsOthers():
900 return True
901 return False
902
903 _userident_name = None
904 _userident_email = None
905
906 @property
907 def UserName(self):
908 """Obtain the user's personal name.
909 """
910 if self._userident_name is None:
911 self._LoadUserIdentity()
912 return self._userident_name
913
914 @property
915 def UserEmail(self):
916 """Obtain the user's email address. This is very likely
917 to be their Gerrit login.
918 """
919 if self._userident_email is None:
920 self._LoadUserIdentity()
921 return self._userident_email
922
923 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900924 u = self.bare_git.var('GIT_COMMITTER_IDENT')
925 m = re.compile("^(.*) <([^>]*)> ").match(u)
926 if m:
927 self._userident_name = m.group(1)
928 self._userident_email = m.group(2)
929 else:
930 self._userident_name = ''
931 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932
933 def GetRemote(self, name):
934 """Get the configuration for a single remote.
935 """
936 return self.config.GetRemote(name)
937
938 def GetBranch(self, name):
939 """Get the configuration for a single branch.
940 """
941 return self.config.GetBranch(name)
942
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700943 def GetBranches(self):
944 """Get all existing local branches.
945 """
946 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700948 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700949
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530950 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700951 if name.startswith(R_HEADS):
952 name = name[len(R_HEADS):]
953 b = self.GetBranch(name)
954 b.current = name == current
955 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900956 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700957 heads[name] = b
958
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530959 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700960 if name.startswith(R_PUB):
961 name = name[len(R_PUB):]
962 b = heads.get(name)
963 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700965
966 return heads
967
Colin Cross5acde752012-03-28 20:15:45 -0700968 def MatchesGroups(self, manifest_groups):
969 """Returns true if the manifest groups specified at init should cause
970 this project to be synced.
971 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700972 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700973
974 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700975 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700976 manifest_groups: "-group1,group2"
977 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500978
979 The special manifest group "default" will match any project that
980 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700981 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500982 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700983 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700984 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500985 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700986
Conley Owens971de8e2012-04-16 10:36:08 -0700987 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700988 for group in expanded_manifest_groups:
989 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700990 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700991 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700992 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700993
Conley Owens971de8e2012-04-16 10:36:08 -0700994 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700996# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700997 def UncommitedFiles(self, get_all=True):
998 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001000 Args:
1001 get_all: a boolean, if True - get information about all different
1002 uncommitted files. If False - return as soon as any kind of
1003 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001004 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001005 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001006 self.work_git.update_index('-q',
1007 '--unmerged',
1008 '--ignore-missing',
1009 '--refresh')
1010 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001011 details.append("rebase in progress")
1012 if not get_all:
1013 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001014
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001015 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1016 if changes:
1017 details.extend(changes)
1018 if not get_all:
1019 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001020
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001021 changes = self.work_git.DiffZ('diff-files').keys()
1022 if changes:
1023 details.extend(changes)
1024 if not get_all:
1025 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001026
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001027 changes = self.work_git.LsOthers()
1028 if changes:
1029 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001030
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001031 return details
1032
1033 def HasChanges(self):
1034 """Returns true if there are uncommitted changes.
1035 """
1036 if self.UncommitedFiles(get_all=False):
1037 return True
1038 else:
1039 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001040
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001041 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001043
1044 Args:
1045 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001046 quiet: If True then only print the project name. Do not print
1047 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001048 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001049 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001050 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001051 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001052 print(file=output_redir)
1053 print('project %s/' % self.relpath, file=output_redir)
1054 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 return
1056
1057 self.work_git.update_index('-q',
1058 '--unmerged',
1059 '--ignore-missing',
1060 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001061 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1063 df = self.work_git.DiffZ('diff-files')
1064 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001065 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001066 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
1068 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001069 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001070 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001071 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001073 if quiet:
1074 out.nl()
1075 return 'DIRTY'
1076
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 branch = self.CurrentBranch
1078 if branch is None:
1079 out.nobranch('(*** NO BRANCH ***)')
1080 else:
1081 out.branch('branch %s', branch)
1082 out.nl()
1083
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001084 if rb:
1085 out.important('prior sync failed; rebase still in progress')
1086 out.nl()
1087
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 paths = list()
1089 paths.extend(di.keys())
1090 paths.extend(df.keys())
1091 paths.extend(do)
1092
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301093 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001094 try:
1095 i = di[p]
1096 except KeyError:
1097 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001099 try:
1100 f = df[p]
1101 except KeyError:
1102 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001103
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001104 if i:
1105 i_status = i.status.upper()
1106 else:
1107 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001109 if f:
1110 f_status = f.status.lower()
1111 else:
1112 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113
1114 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001115 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001116 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 else:
1118 line = ' %s%s\t%s' % (i_status, f_status, p)
1119
1120 if i and not f:
1121 out.added('%s', line)
1122 elif (i and f) or (not i and f):
1123 out.changed('%s', line)
1124 elif not i and not f:
1125 out.untracked('%s', line)
1126 else:
1127 out.write('%s', line)
1128 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001129
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001130 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
pelyad67872d2012-03-28 14:49:58 +03001132 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 """Prints the status of the repository to stdout.
1134 """
1135 out = DiffColoring(self.config)
1136 cmd = ['diff']
1137 if out.is_on:
1138 cmd.append('--color')
1139 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001140 if absolute_paths:
1141 cmd.append('--src-prefix=a/%s/' % self.relpath)
1142 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001144 try:
1145 p = GitCommand(self,
1146 cmd,
1147 capture_stdout=True,
1148 capture_stderr=True)
1149 except GitError as e:
1150 out.nl()
1151 out.project('project %s/' % self.relpath)
1152 out.nl()
1153 out.fail('%s', str(e))
1154 out.nl()
1155 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156 has_diff = False
1157 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001158 if not hasattr(line, 'encode'):
1159 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 if not has_diff:
1161 out.nl()
1162 out.project('project %s/' % self.relpath)
1163 out.nl()
1164 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001165 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001166 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
1168
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001169# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
David Pursehouse8a68ff92012-09-24 12:15:13 +09001171 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 """Was the branch published (uploaded) for code review?
1173 If so, returns the SHA-1 hash of the last published
1174 state for the branch.
1175 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001176 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001177 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001178 try:
1179 return self.bare_git.rev_parse(key)
1180 except GitError:
1181 return None
1182 else:
1183 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001184 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001185 except KeyError:
1186 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
David Pursehouse8a68ff92012-09-24 12:15:13 +09001188 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189 """Prunes any stale published refs.
1190 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001191 if all_refs is None:
1192 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 heads = set()
1194 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301195 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 if name.startswith(R_HEADS):
1197 heads.add(name)
1198 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001199 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301201 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202 n = name[len(R_PUB):]
1203 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001204 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001206 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001207 """List any branches which can be uploaded for review.
1208 """
1209 heads = {}
1210 pubed = {}
1211
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301212 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001214 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001216 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217
1218 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301219 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001220 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001222 if selected_branch and branch != selected_branch:
1223 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001225 rb = self.GetUploadableBranch(branch)
1226 if rb:
1227 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228 return ready
1229
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001230 def GetUploadableBranch(self, branch_name):
1231 """Get a single uploadable branch, or None.
1232 """
1233 branch = self.GetBranch(branch_name)
1234 base = branch.LocalMerge
1235 if branch.LocalMerge:
1236 rb = ReviewableBranch(self, branch, base)
1237 if rb.commits:
1238 return rb
1239 return None
1240
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001241 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001242 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001243 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001244 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001245 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001246 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001247 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001248 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001249 validate_certs=True,
1250 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251 """Uploads the named branch for code review.
1252 """
1253 if branch is None:
1254 branch = self.CurrentBranch
1255 if branch is None:
1256 raise GitError('not currently on a branch')
1257
1258 branch = self.GetBranch(branch)
1259 if not branch.LocalMerge:
1260 raise GitError('branch %s does not track a remote' % branch.name)
1261 if not branch.remote.review:
1262 raise GitError('remote %s has no review url' % branch.remote.name)
1263
Bryan Jacobsf609f912013-05-06 13:36:24 -04001264 if dest_branch is None:
1265 dest_branch = self.dest_branch
1266 if dest_branch is None:
1267 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268 if not dest_branch.startswith(R_HEADS):
1269 dest_branch = R_HEADS + dest_branch
1270
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001271 if not branch.remote.projectname:
1272 branch.remote.projectname = self.name
1273 branch.remote.Save()
1274
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001275 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001276 if url is None:
1277 raise UploadError('review not configured')
1278 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001279
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001280 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001281 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001282
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001283 for push_option in (push_options or []):
1284 cmd.append('-o')
1285 cmd.append(push_option)
1286
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001287 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001288
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001289 if dest_branch.startswith(R_HEADS):
1290 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001291
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001292 upload_type = 'for'
1293 if draft:
1294 upload_type = 'drafts'
1295
1296 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1297 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001298 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001299 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001300 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001301
David Pursehousef25a3702018-11-14 19:01:22 -08001302 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001303 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001304 if notify:
1305 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001306 if private:
1307 opts += ['private']
1308 if wip:
1309 opts += ['wip']
1310 if opts:
1311 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001312 cmd.append(ref_spec)
1313
Anthony King7bdac712014-07-16 12:56:40 +01001314 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001315 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316
1317 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1318 self.bare_git.UpdateRef(R_PUB + branch.name,
1319 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001320 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321
1322
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001323# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Julien Campergue335f5ef2013-10-16 11:02:35 +02001325 def _ExtractArchive(self, tarpath, path=None):
1326 """Extract the given tar on its current location
1327
1328 Args:
1329 - tarpath: The path to the actual tar file
1330
1331 """
1332 try:
1333 with tarfile.open(tarpath, 'r') as tar:
1334 tar.extractall(path=path)
1335 return True
1336 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001337 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001338 return False
1339
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001340 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001341 quiet=False,
1342 is_new=None,
1343 current_branch_only=False,
1344 force_sync=False,
1345 clone_bundle=True,
1346 no_tags=False,
1347 archive=False,
1348 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001349 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001350 submodules=False,
1351 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352 """Perform only the network IO portion of the sync process.
1353 Local working directory/branch state is not affected.
1354 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001355 if archive and not isinstance(self, MetaProject):
1356 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001357 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001358 return False
1359
1360 name = self.relpath.replace('\\', '/')
1361 name = name.replace('/', '_')
1362 tarpath = '%s.tar' % name
1363 topdir = self.manifest.topdir
1364
1365 try:
1366 self._FetchArchive(tarpath, cwd=topdir)
1367 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001368 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001369 return False
1370
1371 # From now on, we only need absolute tarpath
1372 tarpath = os.path.join(topdir, tarpath)
1373
1374 if not self._ExtractArchive(tarpath, path=topdir):
1375 return False
1376 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001377 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001378 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001379 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001380 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001381 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001382 if is_new is None:
1383 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001384 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001385 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001386 else:
1387 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001389
1390 if is_new:
1391 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1392 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001393 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001394 # This works for both absolute and relative alternate directories.
1395 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001396 except IOError:
1397 alt_dir = None
1398 else:
1399 alt_dir = None
1400
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001401 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001402 and alt_dir is None \
1403 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001404 is_new = False
1405
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001406 if not current_branch_only:
1407 if self.sync_c:
1408 current_branch_only = True
1409 elif not self.manifest._loaded:
1410 # Manifest cannot check defaults until it syncs.
1411 current_branch_only = False
1412 elif self.manifest.default.sync_c:
1413 current_branch_only = True
1414
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001415 if not no_tags:
1416 if not self.sync_tags:
1417 no_tags = True
1418
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001419 if self.clone_depth:
1420 depth = self.clone_depth
1421 else:
1422 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1423
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001424 need_to_fetch = not (optimized_fetch and
1425 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001426 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001427 if (need_to_fetch and
1428 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1429 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001430 no_tags=no_tags, prune=prune, depth=depth,
Xin Li745be2e2019-06-03 11:24:30 -07001431 submodules=submodules, force_sync=force_sync,
1432 clone_filter=clone_filter)):
Anthony King7bdac712014-07-16 12:56:40 +01001433 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001434
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001435 mp = self.manifest.manifestProject
1436 dissociate = mp.config.GetBoolean('repo.dissociate')
1437 if dissociate:
1438 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1439 if os.path.exists(alternates_file):
1440 cmd = ['repack', '-a', '-d']
1441 if GitCommand(self, cmd, bare=True).Wait() != 0:
1442 return False
1443 platform_utils.remove(alternates_file)
1444
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001445 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001446 self._InitMRef()
1447 else:
1448 self._InitMirrorHead()
1449 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001450 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001451 except OSError:
1452 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001454
1455 def PostRepoUpgrade(self):
1456 self._InitHooks()
1457
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001458 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001459 if self.manifest.isGitcClient:
1460 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001461 for copyfile in self.copyfiles:
1462 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001463 for linkfile in self.linkfiles:
1464 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465
Julien Camperguedd654222014-01-09 16:21:37 +01001466 def GetCommitRevisionId(self):
1467 """Get revisionId of a commit.
1468
1469 Use this method instead of GetRevisionId to get the id of the commit rather
1470 than the id of the current git object (for example, a tag)
1471
1472 """
1473 if not self.revisionExpr.startswith(R_TAGS):
1474 return self.GetRevisionId(self._allrefs)
1475
1476 try:
1477 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1478 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001479 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1480 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001481
David Pursehouse8a68ff92012-09-24 12:15:13 +09001482 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001483 if self.revisionId:
1484 return self.revisionId
1485
1486 rem = self.GetRemote(self.remote.name)
1487 rev = rem.ToLocal(self.revisionExpr)
1488
David Pursehouse8a68ff92012-09-24 12:15:13 +09001489 if all_refs is not None and rev in all_refs:
1490 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001491
1492 try:
1493 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1494 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001495 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1496 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001497
Martin Kellye4e94d22017-03-21 16:05:12 -07001498 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499 """Perform only the local IO portion of the sync process.
1500 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001501 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001502 if not os.path.exists(self.gitdir):
1503 syncbuf.fail(self,
1504 'Cannot checkout %s due to missing network sync; Run '
1505 '`repo sync -n %s` first.' %
1506 (self.name, self.name))
1507 return
1508
Martin Kellye4e94d22017-03-21 16:05:12 -07001509 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001510 all_refs = self.bare_ref.all
1511 self.CleanPublishedCache(all_refs)
1512 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001513
David Pursehouse1d947b32012-10-25 12:23:11 +09001514 def _doff():
1515 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001516 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001517
Martin Kellye4e94d22017-03-21 16:05:12 -07001518 def _dosubmodules():
1519 self._SyncSubmodules(quiet=True)
1520
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001521 head = self.work_git.GetHead()
1522 if head.startswith(R_HEADS):
1523 branch = head[len(R_HEADS):]
1524 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001525 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001526 except KeyError:
1527 head = None
1528 else:
1529 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001531 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 # Currently on a detached HEAD. The user is assumed to
1533 # not have any local modifications worth worrying about.
1534 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001535 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001536 syncbuf.fail(self, _PriorSyncFailedError())
1537 return
1538
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001539 if head == revid:
1540 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001541 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001542 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001543 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001544 # The copy/linkfile config may have changed.
1545 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001546 return
1547 else:
1548 lost = self._revlist(not_rev(revid), HEAD)
1549 if lost:
1550 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001553 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001554 if submodules:
1555 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001556 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001557 syncbuf.fail(self, e)
1558 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001559 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001560 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001562 if head == revid:
1563 # No changes; don't do anything further.
1564 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001565 # The copy/linkfile config may have changed.
1566 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001567 return
1568
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001571 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001573 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001575 syncbuf.info(self,
1576 "leaving %s; does not track upstream",
1577 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001578 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001579 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001580 if submodules:
1581 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001582 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001583 syncbuf.fail(self, e)
1584 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001585 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001586 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001587
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001588 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001589 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001591 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592 if not_merged:
1593 if upstream_gain:
1594 # The user has published this branch and some of those
1595 # commits are not yet merged upstream. We do not want
1596 # to rewrite the published commits so we punt.
1597 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001598 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001599 "branch %s is published (but not merged) and is now "
1600 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001601 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001602 elif pub == head:
1603 # All published commits are merged, and thus we are a
1604 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001605 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001606 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001607 if submodules:
1608 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001609 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001611 # Examine the local commits not in the remote. Find the
1612 # last one attributed to this user, if any.
1613 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001614 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001615 last_mine = None
1616 cnt_mine = 0
1617 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001618 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001619 if committer_email == self.UserEmail:
1620 last_mine = commit_id
1621 cnt_mine += 1
1622
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001623 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001624 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001625
1626 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001627 syncbuf.fail(self, _DirtyError())
1628 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001629
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001630 # If the upstream switched on us, warn the user.
1631 #
1632 if branch.merge != self.revisionExpr:
1633 if branch.merge and self.revisionExpr:
1634 syncbuf.info(self,
1635 'manifest switched %s...%s',
1636 branch.merge,
1637 self.revisionExpr)
1638 elif branch.merge:
1639 syncbuf.info(self,
1640 'manifest no longer tracks %s',
1641 branch.merge)
1642
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001643 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001644 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001645 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001646 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001647 syncbuf.info(self,
1648 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001649 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001651 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001652 if not ID_RE.match(self.revisionExpr):
1653 # in case of manifest sync the revisionExpr might be a SHA1
1654 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001655 if not branch.merge.startswith('refs/'):
1656 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001657 branch.Save()
1658
Mike Pontillod3153822012-02-28 11:53:24 -08001659 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001660 def _docopyandlink():
1661 self._CopyAndLinkFiles()
1662
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001663 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001664 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001665 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001666 if submodules:
1667 syncbuf.later2(self, _dosubmodules)
1668 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001669 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001671 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001672 if submodules:
1673 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001674 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001675 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001676 syncbuf.fail(self, e)
1677 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001679 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001680 if submodules:
1681 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001683 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684 # dest should already be an absolute path, but src is project relative
1685 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001686 abssrc = os.path.join(self.worktree, src)
1687 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001689 def AddLinkFile(self, src, dest, absdest):
1690 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001691 # make src relative path to dest
1692 absdestdir = os.path.dirname(absdest)
1693 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001694 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001695
James W. Mills24c13082012-04-12 15:04:13 -05001696 def AddAnnotation(self, name, value, keep):
1697 self.annotations.append(_Annotation(name, value, keep))
1698
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001699 def DownloadPatchSet(self, change_id, patch_id):
1700 """Download a single patch set of a single change to FETCH_HEAD.
1701 """
1702 remote = self.GetRemote(self.remote.name)
1703
1704 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001705 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001706 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001707 if GitCommand(self, cmd, bare=True).Wait() != 0:
1708 return None
1709 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001710 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001711 change_id,
1712 patch_id,
1713 self.bare_git.rev_parse('FETCH_HEAD'))
1714
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001716# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001718 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001719 """Create a new branch off the manifest's revision.
1720 """
Simran Basib9a1b732015-08-20 12:19:28 -07001721 if not branch_merge:
1722 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001723 head = self.work_git.GetHead()
1724 if head == (R_HEADS + name):
1725 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726
David Pursehouse8a68ff92012-09-24 12:15:13 +09001727 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001728 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001729 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001730 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001731 capture_stdout=True,
1732 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001733
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001734 branch = self.GetBranch(name)
1735 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001736 branch.merge = branch_merge
1737 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1738 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001739
1740 if revision is None:
1741 revid = self.GetRevisionId(all_refs)
1742 else:
1743 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001744
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001745 if head.startswith(R_HEADS):
1746 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001747 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001748 except KeyError:
1749 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001750 if revid and head and revid == head:
1751 ref = os.path.join(self.gitdir, R_HEADS + name)
1752 try:
1753 os.makedirs(os.path.dirname(ref))
1754 except OSError:
1755 pass
1756 _lwrite(ref, '%s\n' % revid)
1757 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1758 'ref: %s%s\n' % (R_HEADS, name))
1759 branch.Save()
1760 return True
1761
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001762 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001763 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001764 capture_stdout=True,
1765 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001766 branch.Save()
1767 return True
1768 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769
Wink Saville02d79452009-04-10 13:01:24 -07001770 def CheckoutBranch(self, name):
1771 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001772
1773 Args:
1774 name: The name of the branch to checkout.
1775
1776 Returns:
1777 True if the checkout succeeded; False if it didn't; None if the branch
1778 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001779 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001780 rev = R_HEADS + name
1781 head = self.work_git.GetHead()
1782 if head == rev:
1783 # Already on the branch
1784 #
1785 return True
Wink Saville02d79452009-04-10 13:01:24 -07001786
David Pursehouse8a68ff92012-09-24 12:15:13 +09001787 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001788 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001789 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001790 except KeyError:
1791 # Branch does not exist in this project
1792 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001793 return None
Wink Saville02d79452009-04-10 13:01:24 -07001794
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001795 if head.startswith(R_HEADS):
1796 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001797 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001798 except KeyError:
1799 head = None
1800
1801 if head == revid:
1802 # Same revision; just update HEAD to point to the new
1803 # target branch, but otherwise take no other action.
1804 #
1805 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1806 'ref: %s%s\n' % (R_HEADS, name))
1807 return True
1808
1809 return GitCommand(self,
1810 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001811 capture_stdout=True,
1812 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001813
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001814 def AbandonBranch(self, name):
1815 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001816
1817 Args:
1818 name: The name of the branch to abandon.
1819
1820 Returns:
1821 True if the abandon succeeded; False if it didn't; None if the branch
1822 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001823 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001824 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001825 all_refs = self.bare_ref.all
1826 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001827 # Doesn't exist
1828 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001829
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001830 head = self.work_git.GetHead()
1831 if head == rev:
1832 # We can't destroy the branch while we are sitting
1833 # on it. Switch to a detached HEAD.
1834 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001835 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001836
David Pursehouse8a68ff92012-09-24 12:15:13 +09001837 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001838 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001839 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1840 '%s\n' % revid)
1841 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001842 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001843
1844 return GitCommand(self,
1845 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001846 capture_stdout=True,
1847 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001848
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001849 def PruneHeads(self):
1850 """Prune any topic branches already merged into upstream.
1851 """
1852 cb = self.CurrentBranch
1853 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001854 left = self._allrefs
1855 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001856 if name.startswith(R_HEADS):
1857 name = name[len(R_HEADS):]
1858 if cb is None or name != cb:
1859 kill.append(name)
1860
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001861 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001862 if cb is not None \
1863 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001864 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001865 self.work_git.DetachHead(HEAD)
1866 kill.append(cb)
1867
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001868 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001869 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001870
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001871 try:
1872 self.bare_git.DetachHead(rev)
1873
1874 b = ['branch', '-d']
1875 b.extend(kill)
1876 b = GitCommand(self, b, bare=True,
1877 capture_stdout=True,
1878 capture_stderr=True)
1879 b.Wait()
1880 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001881 if ID_RE.match(old):
1882 self.bare_git.DetachHead(old)
1883 else:
1884 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001885 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001886
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001887 for branch in kill:
1888 if (R_HEADS + branch) not in left:
1889 self.CleanPublishedCache()
1890 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001891
1892 if cb and cb not in kill:
1893 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001894 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001895
1896 kept = []
1897 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001898 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001899 branch = self.GetBranch(branch)
1900 base = branch.LocalMerge
1901 if not base:
1902 base = rev
1903 kept.append(ReviewableBranch(self, branch, base))
1904 return kept
1905
1906
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001907# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001908
1909 def GetRegisteredSubprojects(self):
1910 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001911
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001912 def rec(subprojects):
1913 if not subprojects:
1914 return
1915 result.extend(subprojects)
1916 for p in subprojects:
1917 rec(p.subprojects)
1918 rec(self.subprojects)
1919 return result
1920
1921 def _GetSubmodules(self):
1922 # Unfortunately we cannot call `git submodule status --recursive` here
1923 # because the working tree might not exist yet, and it cannot be used
1924 # without a working tree in its current implementation.
1925
1926 def get_submodules(gitdir, rev):
1927 # Parse .gitmodules for submodule sub_paths and sub_urls
1928 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1929 if not sub_paths:
1930 return []
1931 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1932 # revision of submodule repository
1933 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1934 submodules = []
1935 for sub_path, sub_url in zip(sub_paths, sub_urls):
1936 try:
1937 sub_rev = sub_revs[sub_path]
1938 except KeyError:
1939 # Ignore non-exist submodules
1940 continue
1941 submodules.append((sub_rev, sub_path, sub_url))
1942 return submodules
1943
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001944 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1945 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001946
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001947 def parse_gitmodules(gitdir, rev):
1948 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1949 try:
Anthony King7bdac712014-07-16 12:56:40 +01001950 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1951 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001952 except GitError:
1953 return [], []
1954 if p.Wait() != 0:
1955 return [], []
1956
1957 gitmodules_lines = []
1958 fd, temp_gitmodules_path = tempfile.mkstemp()
1959 try:
1960 os.write(fd, p.stdout)
1961 os.close(fd)
1962 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001963 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1964 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001965 if p.Wait() != 0:
1966 return [], []
1967 gitmodules_lines = p.stdout.split('\n')
1968 except GitError:
1969 return [], []
1970 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001971 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001972
1973 names = set()
1974 paths = {}
1975 urls = {}
1976 for line in gitmodules_lines:
1977 if not line:
1978 continue
1979 m = re_path.match(line)
1980 if m:
1981 names.add(m.group(1))
1982 paths[m.group(1)] = m.group(2)
1983 continue
1984 m = re_url.match(line)
1985 if m:
1986 names.add(m.group(1))
1987 urls[m.group(1)] = m.group(2)
1988 continue
1989 names = sorted(names)
1990 return ([paths.get(name, '') for name in names],
1991 [urls.get(name, '') for name in names])
1992
1993 def git_ls_tree(gitdir, rev, paths):
1994 cmd = ['ls-tree', rev, '--']
1995 cmd.extend(paths)
1996 try:
Anthony King7bdac712014-07-16 12:56:40 +01001997 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1998 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001999 except GitError:
2000 return []
2001 if p.Wait() != 0:
2002 return []
2003 objects = {}
2004 for line in p.stdout.split('\n'):
2005 if not line.strip():
2006 continue
2007 object_rev, object_path = line.split()[2:4]
2008 objects[object_path] = object_rev
2009 return objects
2010
2011 try:
2012 rev = self.GetRevisionId()
2013 except GitError:
2014 return []
2015 return get_submodules(self.gitdir, rev)
2016
2017 def GetDerivedSubprojects(self):
2018 result = []
2019 if not self.Exists:
2020 # If git repo does not exist yet, querying its submodules will
2021 # mess up its states; so return here.
2022 return result
2023 for rev, path, url in self._GetSubmodules():
2024 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002025 relpath, worktree, gitdir, objdir = \
2026 self.manifest.GetSubprojectPaths(self, name, path)
2027 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002028 if project:
2029 result.extend(project.GetDerivedSubprojects())
2030 continue
David James8d201162013-10-11 17:03:19 -07002031
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002032 if url.startswith('..'):
2033 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002034 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002035 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002036 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002037 review=self.remote.review,
2038 revision=self.remote.revision)
2039 subproject = Project(manifest=self.manifest,
2040 name=name,
2041 remote=remote,
2042 gitdir=gitdir,
2043 objdir=objdir,
2044 worktree=worktree,
2045 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002046 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002047 revisionId=rev,
2048 rebase=self.rebase,
2049 groups=self.groups,
2050 sync_c=self.sync_c,
2051 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002052 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002053 parent=self,
2054 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002055 result.append(subproject)
2056 result.extend(subproject.GetDerivedSubprojects())
2057 return result
2058
2059
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002060# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002061 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002062 try:
2063 # if revision (sha or tag) is not present then following function
2064 # throws an error.
2065 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2066 return True
2067 except GitError:
2068 # There is no such persistent revision. We have to fetch it.
2069 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002070
Julien Campergue335f5ef2013-10-16 11:02:35 +02002071 def _FetchArchive(self, tarpath, cwd=None):
2072 cmd = ['archive', '-v', '-o', tarpath]
2073 cmd.append('--remote=%s' % self.remote.url)
2074 cmd.append('--prefix=%s/' % self.relpath)
2075 cmd.append(self.revisionExpr)
2076
2077 command = GitCommand(self, cmd, cwd=cwd,
2078 capture_stdout=True,
2079 capture_stderr=True)
2080
2081 if command.Wait() != 0:
2082 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2083
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002084 def _RemoteFetch(self, name=None,
2085 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002086 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002087 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002088 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002089 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002090 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002091 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002092 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002093 force_sync=False,
2094 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002095
2096 is_sha1 = False
2097 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002098 # The depth should not be used when fetching to a mirror because
2099 # it will result in a shallow repository that cannot be cloned or
2100 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002101 # The repo project should also never be synced with partial depth.
2102 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2103 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002104
Shawn Pearce69e04d82014-01-29 12:48:54 -08002105 if depth:
2106 current_branch_only = True
2107
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002108 if ID_RE.match(self.revisionExpr) is not None:
2109 is_sha1 = True
2110
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002111 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002112 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002113 # this is a tag and its sha1 value should never change
2114 tag_name = self.revisionExpr[len(R_TAGS):]
2115
2116 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002117 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002118 if not quiet:
2119 print('Skipped fetching project %s (already have persistent ref)'
2120 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002121 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002122 if is_sha1 and not depth:
2123 # When syncing a specific commit and --depth is not set:
2124 # * if upstream is explicitly specified and is not a sha1, fetch only
2125 # upstream as users expect only upstream to be fetch.
2126 # Note: The commit might not be in upstream in which case the sync
2127 # will fail.
2128 # * otherwise, fetch all branches to make sure we end up with the
2129 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002130 if self.upstream:
2131 current_branch_only = not ID_RE.match(self.upstream)
2132 else:
2133 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 if not name:
2136 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002137
2138 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002139 remote = self.GetRemote(name)
2140 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002141 ssh_proxy = True
2142
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002144 if alt_dir and 'objects' == os.path.basename(alt_dir):
2145 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002146 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2147 remote = self.GetRemote(name)
2148
David Pursehouse8a68ff92012-09-24 12:15:13 +09002149 all_refs = self.bare_ref.all
2150 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151 tmp = set()
2152
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302153 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002154 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002155 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002156 all_refs[r] = ref_id
2157 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002158 continue
2159
David Pursehouse8a68ff92012-09-24 12:15:13 +09002160 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002161 continue
2162
David Pursehouse8a68ff92012-09-24 12:15:13 +09002163 r = 'refs/_alt/%s' % ref_id
2164 all_refs[r] = ref_id
2165 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002166 tmp.add(r)
2167
heping3d7bbc92017-04-12 19:51:47 +08002168 tmp_packed_lines = []
2169 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002170
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302171 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002172 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002173 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002174 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002175 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002176
heping3d7bbc92017-04-12 19:51:47 +08002177 tmp_packed = ''.join(tmp_packed_lines)
2178 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002179 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002180 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002181 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002182
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002183 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002184
Xin Li745be2e2019-06-03 11:24:30 -07002185 if clone_filter:
2186 git_require((2, 19, 0), fail=True, msg='partial clones')
2187 cmd.append('--filter=%s' % clone_filter)
2188 self.config.SetString('extensions.partialclone', self.remote.name)
2189
Conley Owensf97e8382015-01-21 11:12:46 -08002190 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002191 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002192 else:
2193 # If this repo has shallow objects, then we don't know which refs have
2194 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2195 # do this with projects that don't have shallow objects, since it is less
2196 # efficient.
2197 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2198 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002199
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002200 if quiet:
2201 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002202 if not self.worktree:
2203 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002204 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002205
Xin Lia2cd6ae2019-09-16 10:55:41 -07002206 spec = []
2207
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002208 # If using depth then we should not get all the tags since they may
2209 # be outside of the depth.
2210 if no_tags or depth:
2211 cmd.append('--no-tags')
2212 else:
2213 cmd.append('--tags')
Xin Lia2cd6ae2019-09-16 10:55:41 -07002214 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002215
Mike Frysingere57f1142019-03-18 21:27:54 -04002216 if force_sync:
2217 cmd.append('--force')
2218
David Pursehouse74cfd272015-10-14 10:50:15 +09002219 if prune:
2220 cmd.append('--prune')
2221
Martin Kellye4e94d22017-03-21 16:05:12 -07002222 if submodules:
2223 cmd.append('--recurse-submodules=on-demand')
2224
Brian Harring14a66742012-09-28 20:21:57 -07002225 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002226 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002227 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002228 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002229 spec.append('tag')
2230 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002231
David Pursehouse403b64e2015-04-27 10:41:33 +09002232 if not self.manifest.IsMirror:
2233 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002234 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002235 # Shallow checkout of a specific commit, fetch from that commit and not
2236 # the heads only as the commit might be deeper in the history.
2237 spec.append(branch)
2238 else:
2239 if is_sha1:
2240 branch = self.upstream
2241 if branch is not None and branch.strip():
2242 if not branch.startswith('refs/'):
2243 branch = R_HEADS + branch
2244 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002245 cmd.extend(spec)
2246
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002247 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002248 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002249 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002250 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002251 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002252 ok = True
2253 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002254 # If needed, run the 'git remote prune' the first time through the loop
2255 elif (not _i and
2256 "error:" in gitcmd.stderr and
2257 "git remote prune" in gitcmd.stderr):
2258 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002259 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002260 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002261 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002262 break
2263 continue
Brian Harring14a66742012-09-28 20:21:57 -07002264 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002265 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2266 # in sha1 mode, we just tried sync'ing from the upstream field; it
2267 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002268 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002269 elif ret < 0:
2270 # Git died with a signal, exit immediately
2271 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002272 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002273
2274 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002275 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002276 if old_packed != '':
2277 _lwrite(packed_refs, old_packed)
2278 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002279 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002280 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002281
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002282 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002283 # We just synced the upstream given branch; verify we
2284 # got what we wanted, else trigger a second run of all
2285 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002286 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002287 if current_branch_only and depth:
2288 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002289 return self._RemoteFetch(name=name,
2290 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002291 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002292 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002293 else:
2294 # Avoid infinite recursion: sync all branches with depth set to None
2295 return self._RemoteFetch(name=name, current_branch_only=False,
2296 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002297 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002298
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002299 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002300
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002301 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002302 if initial and \
2303 (self.manifest.manifestProject.config.GetString('repo.depth') or
2304 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002305 return False
2306
2307 remote = self.GetRemote(self.remote.name)
2308 bundle_url = remote.url + '/clone.bundle'
2309 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002310 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2311 'persistent-http',
2312 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002313 return False
2314
2315 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2316 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2317
2318 exist_dst = os.path.exists(bundle_dst)
2319 exist_tmp = os.path.exists(bundle_tmp)
2320
2321 if not initial and not exist_dst and not exist_tmp:
2322 return False
2323
2324 if not exist_dst:
2325 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2326 if not exist_dst:
2327 return False
2328
2329 cmd = ['fetch']
2330 if quiet:
2331 cmd.append('--quiet')
2332 if not self.worktree:
2333 cmd.append('--update-head-ok')
2334 cmd.append(bundle_dst)
2335 for f in remote.fetch:
2336 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002337 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002338
2339 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002340 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002341 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002342 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002343 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002344 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002345
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002346 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002347 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002348 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002349
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002350 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002351 if quiet:
2352 cmd += ['--silent']
2353 if os.path.exists(tmpPath):
2354 size = os.stat(tmpPath).st_size
2355 if size >= 1024:
2356 cmd += ['--continue-at', '%d' % (size,)]
2357 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002358 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002359 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002360 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002361 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002362 if proxy:
2363 cmd += ['--proxy', proxy]
2364 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2365 cmd += ['--proxy', os.environ['http_proxy']]
2366 if srcUrl.startswith('persistent-https'):
2367 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2368 elif srcUrl.startswith('persistent-http'):
2369 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002370 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002371
Dave Borowitz137d0132015-01-02 11:12:54 -08002372 if IsTrace():
2373 Trace('%s', ' '.join(cmd))
2374 try:
2375 proc = subprocess.Popen(cmd)
2376 except OSError:
2377 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002378
Dave Borowitz137d0132015-01-02 11:12:54 -08002379 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002380
Dave Borowitz137d0132015-01-02 11:12:54 -08002381 if curlret == 22:
2382 # From curl man page:
2383 # 22: HTTP page not retrieved. The requested url was not found or
2384 # returned another error with the HTTP error code being 400 or above.
2385 # This return code only appears if -f, --fail is used.
2386 if not quiet:
2387 print("Server does not provide clone.bundle; ignoring.",
2388 file=sys.stderr)
2389 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002390
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002391 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002392 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002393 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002394 return True
2395 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002396 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002397 return False
2398 else:
2399 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002400
Kris Giesingc8d882a2014-12-23 13:02:32 -08002401 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002402 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002403 with open(path, 'rb') as f:
2404 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002405 return True
2406 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002407 if not quiet:
2408 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002409 return False
2410 except OSError:
2411 return False
2412
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002413 def _Checkout(self, rev, quiet=False):
2414 cmd = ['checkout']
2415 if quiet:
2416 cmd.append('-q')
2417 cmd.append(rev)
2418 cmd.append('--')
2419 if GitCommand(self, cmd).Wait() != 0:
2420 if self._allrefs:
2421 raise GitError('%s checkout %s ' % (self.name, rev))
2422
Anthony King7bdac712014-07-16 12:56:40 +01002423 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002424 cmd = ['cherry-pick']
2425 cmd.append(rev)
2426 cmd.append('--')
2427 if GitCommand(self, cmd).Wait() != 0:
2428 if self._allrefs:
2429 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2430
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302431 def _LsRemote(self, refs):
2432 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302433 p = GitCommand(self, cmd, capture_stdout=True)
2434 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002435 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302436 return None
2437
Anthony King7bdac712014-07-16 12:56:40 +01002438 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002439 cmd = ['revert']
2440 cmd.append('--no-edit')
2441 cmd.append(rev)
2442 cmd.append('--')
2443 if GitCommand(self, cmd).Wait() != 0:
2444 if self._allrefs:
2445 raise GitError('%s revert %s ' % (self.name, rev))
2446
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 def _ResetHard(self, rev, quiet=True):
2448 cmd = ['reset', '--hard']
2449 if quiet:
2450 cmd.append('-q')
2451 cmd.append(rev)
2452 if GitCommand(self, cmd).Wait() != 0:
2453 raise GitError('%s reset --hard %s ' % (self.name, rev))
2454
Martin Kellye4e94d22017-03-21 16:05:12 -07002455 def _SyncSubmodules(self, quiet=True):
2456 cmd = ['submodule', 'update', '--init', '--recursive']
2457 if quiet:
2458 cmd.append('-q')
2459 if GitCommand(self, cmd).Wait() != 0:
2460 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2461
Anthony King7bdac712014-07-16 12:56:40 +01002462 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002463 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002464 if onto is not None:
2465 cmd.extend(['--onto', onto])
2466 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002467 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002468 raise GitError('%s rebase %s ' % (self.name, upstream))
2469
Pierre Tardy3d125942012-05-04 12:18:12 +02002470 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002471 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002472 if ffonly:
2473 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474 if GitCommand(self, cmd).Wait() != 0:
2475 raise GitError('%s merge %s ' % (self.name, head))
2476
Kevin Degiabaa7f32014-11-12 11:27:45 -07002477 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002478 init_git_dir = not os.path.exists(self.gitdir)
2479 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002480 try:
2481 # Initialize the bare repository, which contains all of the objects.
2482 if init_obj_dir:
2483 os.makedirs(self.objdir)
2484 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002485
Kevin Degib1a07b82015-07-27 13:33:43 -06002486 # If we have a separate directory to hold refs, initialize it as well.
2487 if self.objdir != self.gitdir:
2488 if init_git_dir:
2489 os.makedirs(self.gitdir)
2490
2491 if init_obj_dir or init_git_dir:
2492 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2493 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002494 try:
2495 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2496 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002497 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002498 print("Retrying clone after deleting %s" %
2499 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002500 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002501 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2502 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002503 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002504 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002505 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2506 except:
2507 raise e
2508 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002509
Kevin Degi384b3c52014-10-16 16:02:58 -06002510 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002511 mp = self.manifest.manifestProject
2512 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002513
Kevin Degib1a07b82015-07-27 13:33:43 -06002514 if ref_dir or mirror_git:
2515 if not mirror_git:
2516 mirror_git = os.path.join(ref_dir, self.name + '.git')
2517 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2518 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002519
Kevin Degib1a07b82015-07-27 13:33:43 -06002520 if os.path.exists(mirror_git):
2521 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002522
Kevin Degib1a07b82015-07-27 13:33:43 -06002523 elif os.path.exists(repo_git):
2524 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002525
Kevin Degib1a07b82015-07-27 13:33:43 -06002526 else:
2527 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002528
Kevin Degib1a07b82015-07-27 13:33:43 -06002529 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002530 if not os.path.isabs(ref_dir):
2531 # The alternate directory is relative to the object database.
2532 ref_dir = os.path.relpath(ref_dir,
2533 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002534 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2535 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002536
Kevin Degib1a07b82015-07-27 13:33:43 -06002537 self._UpdateHooks()
2538
2539 m = self.manifest.manifestProject.config
2540 for key in ['user.name', 'user.email']:
2541 if m.Has(key, include_defaults=False):
2542 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002543 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002544 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002545 if self.manifest.IsMirror:
2546 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002547 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002548 self.config.SetString('core.bare', None)
2549 except Exception:
2550 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002551 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002552 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002553 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002554 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555
Jimmie Westera0444582012-10-24 13:44:42 +02002556 def _UpdateHooks(self):
2557 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002558 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002559
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002560 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002561 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002562 if not os.path.exists(hooks):
2563 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002564 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002565 name = os.path.basename(stock_hook)
2566
Victor Boivie65e0f352011-04-18 11:23:29 +02002567 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002568 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002569 # Don't install a Gerrit Code Review hook if this
2570 # project does not appear to use it for reviews.
2571 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002572 # Since the manifest project is one of those, but also
2573 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002574 continue
2575
2576 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002577 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002578 continue
2579 if os.path.exists(dst):
2580 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002581 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002582 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002583 _warn("%s: Not replacing locally modified %s hook",
2584 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002585 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002586 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002587 platform_utils.symlink(
2588 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002589 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002590 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002591 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002592 else:
2593 raise
2594
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002596 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002598 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002599 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002600 remote.review = self.remote.review
2601 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002603 if self.worktree:
2604 remote.ResetFetch(mirror=False)
2605 else:
2606 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002607 remote.Save()
2608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002609 def _InitMRef(self):
2610 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002611 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002613 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002614 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002615
2616 def _InitAnyMRef(self, ref):
2617 cur = self.bare_ref.symref(ref)
2618
2619 if self.revisionId:
2620 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2621 msg = 'manifest set to %s' % self.revisionId
2622 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002623 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002624 else:
2625 remote = self.GetRemote(self.remote.name)
2626 dst = remote.ToLocal(self.revisionExpr)
2627 if cur != dst:
2628 msg = 'manifest set to %s' % self.revisionExpr
2629 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002630
Kevin Degi384b3c52014-10-16 16:02:58 -06002631 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002632 symlink_files = self.shareable_files[:]
2633 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002634 if share_refs:
2635 symlink_files += self.working_tree_files
2636 symlink_dirs += self.working_tree_dirs
2637 to_symlink = symlink_files + symlink_dirs
2638 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002639 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002640 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002641 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002642 # Fail if the links are pointing to the wrong place
2643 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002644 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002645 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002646 'work tree. If you\'re comfortable with the '
2647 'possibility of losing the work tree\'s git metadata,'
2648 ' use `repo sync --force-sync {0}` to '
2649 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002650
David James8d201162013-10-11 17:03:19 -07002651 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2652 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2653
2654 Args:
2655 gitdir: The bare git repository. Must already be initialized.
2656 dotgit: The repository you would like to initialize.
2657 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2658 Only one work tree can store refs under a given |gitdir|.
2659 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2660 This saves you the effort of initializing |dotgit| yourself.
2661 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002662 symlink_files = self.shareable_files[:]
2663 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002664 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002665 symlink_files += self.working_tree_files
2666 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002667 to_symlink = symlink_files + symlink_dirs
2668
2669 to_copy = []
2670 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002671 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002672
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002673 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002674 for name in set(to_copy).union(to_symlink):
2675 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002676 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002677 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002678
Kevin Degi384b3c52014-10-16 16:02:58 -06002679 if os.path.lexists(dst):
2680 continue
David James8d201162013-10-11 17:03:19 -07002681
2682 # If the source dir doesn't exist, create an empty dir.
2683 if name in symlink_dirs and not os.path.lexists(src):
2684 os.makedirs(src)
2685
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002686 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002687 platform_utils.symlink(
2688 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002689 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002690 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002691 shutil.copytree(src, dst)
2692 elif os.path.isfile(src):
2693 shutil.copy(src, dst)
2694
Conley Owens80b87fe2014-05-09 17:13:44 -07002695 # If the source file doesn't exist, ensure the destination
2696 # file doesn't either.
2697 if name in symlink_files and not os.path.lexists(src):
2698 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002699 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002700 except OSError:
2701 pass
2702
David James8d201162013-10-11 17:03:19 -07002703 except OSError as e:
2704 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002705 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002706 else:
2707 raise
2708
Martin Kellye4e94d22017-03-21 16:05:12 -07002709 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002710 realdotgit = os.path.join(self.worktree, '.git')
2711 tmpdotgit = realdotgit + '.tmp'
2712 init_dotgit = not os.path.exists(realdotgit)
2713 if init_dotgit:
2714 dotgit = tmpdotgit
2715 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2716 os.makedirs(tmpdotgit)
2717 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2718 copy_all=False)
2719 else:
2720 dotgit = realdotgit
2721
Kevin Degib1a07b82015-07-27 13:33:43 -06002722 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002723 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2724 except GitError as e:
2725 if force_sync and not init_dotgit:
2726 try:
2727 platform_utils.rmtree(dotgit)
2728 return self._InitWorkTree(force_sync=False, submodules=submodules)
2729 except:
2730 raise e
2731 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002732
Mike Frysingerf4545122019-11-11 04:34:16 -05002733 if init_dotgit:
2734 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002735
Mike Frysingerf4545122019-11-11 04:34:16 -05002736 # Now that the .git dir is fully set up, move it to its final home.
2737 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738
Mike Frysingerf4545122019-11-11 04:34:16 -05002739 # Finish checking out the worktree.
2740 cmd = ['read-tree', '--reset', '-u']
2741 cmd.append('-v')
2742 cmd.append(HEAD)
2743 if GitCommand(self, cmd).Wait() != 0:
2744 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002745
Mike Frysingerf4545122019-11-11 04:34:16 -05002746 if submodules:
2747 self._SyncSubmodules(quiet=True)
2748 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002749
Renaud Paquay788e9622017-01-27 11:41:12 -08002750 def _get_symlink_error_message(self):
2751 if platform_utils.isWindows():
2752 return ('Unable to create symbolic link. Please re-run the command as '
2753 'Administrator, or see '
2754 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2755 'for other options.')
2756 return 'filesystem must support symlinks'
2757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002758 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002759 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002760
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002761 def _revlist(self, *args, **kw):
2762 a = []
2763 a.extend(args)
2764 a.append('--')
2765 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002766
2767 @property
2768 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002769 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002770
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002771 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002772 """Get logs between two revisions of this project."""
2773 comp = '..'
2774 if rev1:
2775 revs = [rev1]
2776 if rev2:
2777 revs.extend([comp, rev2])
2778 cmd = ['log', ''.join(revs)]
2779 out = DiffColoring(self.config)
2780 if out.is_on and color:
2781 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002782 if pretty_format is not None:
2783 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002784 if oneline:
2785 cmd.append('--oneline')
2786
2787 try:
2788 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2789 if log.Wait() == 0:
2790 return log.stdout
2791 except GitError:
2792 # worktree may not exist if groups changed for example. In that case,
2793 # try in gitdir instead.
2794 if not os.path.exists(self.worktree):
2795 return self.bare_git.log(*cmd[1:])
2796 else:
2797 raise
2798 return None
2799
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002800 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2801 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002802 """Get the list of logs from this revision to given revisionId"""
2803 logs = {}
2804 selfId = self.GetRevisionId(self._allrefs)
2805 toId = toProject.GetRevisionId(toProject._allrefs)
2806
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002807 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2808 pretty_format=pretty_format)
2809 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2810 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002811 return logs
2812
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002813 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002814
David James8d201162013-10-11 17:03:19 -07002815 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002816 self._project = project
2817 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002818 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002819
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002820 def LsOthers(self):
2821 p = GitCommand(self._project,
2822 ['ls-files',
2823 '-z',
2824 '--others',
2825 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002826 bare=False,
David James8d201162013-10-11 17:03:19 -07002827 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002828 capture_stdout=True,
2829 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002830 if p.Wait() == 0:
2831 out = p.stdout
2832 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002833 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002834 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002835 return []
2836
2837 def DiffZ(self, name, *args):
2838 cmd = [name]
2839 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002840 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002841 cmd.extend(args)
2842 p = GitCommand(self._project,
2843 cmd,
David James8d201162013-10-11 17:03:19 -07002844 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002845 bare=False,
2846 capture_stdout=True,
2847 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002848 try:
2849 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002850 if not hasattr(out, 'encode'):
2851 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002852 r = {}
2853 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002854 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002855 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002856 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002857 info = next(out)
2858 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002859 except StopIteration:
2860 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002861
2862 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002863
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002864 def __init__(self, path, omode, nmode, oid, nid, state):
2865 self.path = path
2866 self.src_path = None
2867 self.old_mode = omode
2868 self.new_mode = nmode
2869 self.old_id = oid
2870 self.new_id = nid
2871
2872 if len(state) == 1:
2873 self.status = state
2874 self.level = None
2875 else:
2876 self.status = state[:1]
2877 self.level = state[1:]
2878 while self.level.startswith('0'):
2879 self.level = self.level[1:]
2880
2881 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002882 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002883 if info.status in ('R', 'C'):
2884 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002885 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002886 r[info.path] = info
2887 return r
2888 finally:
2889 p.Wait()
2890
2891 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002892 if self._bare:
2893 path = os.path.join(self._project.gitdir, HEAD)
2894 else:
2895 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002896 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002897 with open(path) as fd:
2898 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002899 except IOError as e:
2900 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002901 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302902 line = line.decode()
2903 except AttributeError:
2904 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002905 if line.startswith('ref: '):
2906 return line[5:-1]
2907 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002908
2909 def SetHead(self, ref, message=None):
2910 cmdv = []
2911 if message is not None:
2912 cmdv.extend(['-m', message])
2913 cmdv.append(HEAD)
2914 cmdv.append(ref)
2915 self.symbolic_ref(*cmdv)
2916
2917 def DetachHead(self, new, message=None):
2918 cmdv = ['--no-deref']
2919 if message is not None:
2920 cmdv.extend(['-m', message])
2921 cmdv.append(HEAD)
2922 cmdv.append(new)
2923 self.update_ref(*cmdv)
2924
2925 def UpdateRef(self, name, new, old=None,
2926 message=None,
2927 detach=False):
2928 cmdv = []
2929 if message is not None:
2930 cmdv.extend(['-m', message])
2931 if detach:
2932 cmdv.append('--no-deref')
2933 cmdv.append(name)
2934 cmdv.append(new)
2935 if old is not None:
2936 cmdv.append(old)
2937 self.update_ref(*cmdv)
2938
2939 def DeleteRef(self, name, old=None):
2940 if not old:
2941 old = self.rev_parse(name)
2942 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002943 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002944
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002945 def rev_list(self, *args, **kw):
2946 if 'format' in kw:
2947 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2948 else:
2949 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002950 cmdv.extend(args)
2951 p = GitCommand(self._project,
2952 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002953 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002954 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002955 capture_stdout=True,
2956 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002957 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002958 raise GitError('%s rev-list %s: %s' %
2959 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002960 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002961
2962 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002963 """Allow arbitrary git commands using pythonic syntax.
2964
2965 This allows you to do things like:
2966 git_obj.rev_parse('HEAD')
2967
2968 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2969 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002970 Any other positional arguments will be passed to the git command, and the
2971 following keyword arguments are supported:
2972 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002973
2974 Args:
2975 name: The name of the git command to call. Any '_' characters will
2976 be replaced with '-'.
2977
2978 Returns:
2979 A callable object that will try to call git with the named command.
2980 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002981 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002982
Dave Borowitz091f8932012-10-23 17:01:04 -07002983 def runner(*args, **kwargs):
2984 cmdv = []
2985 config = kwargs.pop('config', None)
2986 for k in kwargs:
2987 raise TypeError('%s() got an unexpected keyword argument %r'
2988 % (name, k))
2989 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002990 if not git_require((1, 7, 2)):
2991 raise ValueError('cannot set config on command line for %s()'
2992 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302993 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002994 cmdv.append('-c')
2995 cmdv.append('%s=%s' % (k, v))
2996 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002997 cmdv.extend(args)
2998 p = GitCommand(self._project,
2999 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003000 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003001 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003002 capture_stdout=True,
3003 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003004 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003005 raise GitError('%s %s: %s' %
3006 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003007 r = p.stdout
3008 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3009 return r[:-1]
3010 return r
3011 return runner
3012
3013
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003014class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003015
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003016 def __str__(self):
3017 return 'prior sync failed; rebase still in progress'
3018
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003019
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003020class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003021
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003022 def __str__(self):
3023 return 'contains uncommitted changes'
3024
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003025
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003026class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003027
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003028 def __init__(self, project, text):
3029 self.project = project
3030 self.text = text
3031
3032 def Print(self, syncbuf):
3033 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3034 syncbuf.out.nl()
3035
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003036
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003037class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003038
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003039 def __init__(self, project, why):
3040 self.project = project
3041 self.why = why
3042
3043 def Print(self, syncbuf):
3044 syncbuf.out.fail('error: %s/: %s',
3045 self.project.relpath,
3046 str(self.why))
3047 syncbuf.out.nl()
3048
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003049
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003050class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003051
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003052 def __init__(self, project, action):
3053 self.project = project
3054 self.action = action
3055
3056 def Run(self, syncbuf):
3057 out = syncbuf.out
3058 out.project('project %s/', self.project.relpath)
3059 out.nl()
3060 try:
3061 self.action()
3062 out.nl()
3063 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003064 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003065 out.nl()
3066 return False
3067
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003068
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003069class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003070
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003071 def __init__(self, config):
3072 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003073 self.project = self.printer('header', attr='bold')
3074 self.info = self.printer('info')
3075 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003076
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003077
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003078class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003079
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003080 def __init__(self, config, detach_head=False):
3081 self._messages = []
3082 self._failures = []
3083 self._later_queue1 = []
3084 self._later_queue2 = []
3085
3086 self.out = _SyncColoring(config)
3087 self.out.redirect(sys.stderr)
3088
3089 self.detach_head = detach_head
3090 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003091 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003092
3093 def info(self, project, fmt, *args):
3094 self._messages.append(_InfoMessage(project, fmt % args))
3095
3096 def fail(self, project, err=None):
3097 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003098 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003099
3100 def later1(self, project, what):
3101 self._later_queue1.append(_Later(project, what))
3102
3103 def later2(self, project, what):
3104 self._later_queue2.append(_Later(project, what))
3105
3106 def Finish(self):
3107 self._PrintMessages()
3108 self._RunLater()
3109 self._PrintMessages()
3110 return self.clean
3111
David Rileye0684ad2017-04-05 00:02:59 -07003112 def Recently(self):
3113 recent_clean = self.recent_clean
3114 self.recent_clean = True
3115 return recent_clean
3116
3117 def _MarkUnclean(self):
3118 self.clean = False
3119 self.recent_clean = False
3120
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003121 def _RunLater(self):
3122 for q in ['_later_queue1', '_later_queue2']:
3123 if not self._RunQueue(q):
3124 return
3125
3126 def _RunQueue(self, queue):
3127 for m in getattr(self, queue):
3128 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003129 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003130 return False
3131 setattr(self, queue, [])
3132 return True
3133
3134 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003135 if self._messages or self._failures:
3136 if os.isatty(2):
3137 self.out.write(progress.CSI_ERASE_LINE)
3138 self.out.write('\r')
3139
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003140 for m in self._messages:
3141 m.Print(self)
3142 for m in self._failures:
3143 m.Print(self)
3144
3145 self._messages = []
3146 self._failures = []
3147
3148
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003149class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003150
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151 """A special project housed under .repo.
3152 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003153
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003154 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003155 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003156 manifest=manifest,
3157 name=name,
3158 gitdir=gitdir,
3159 objdir=gitdir,
3160 worktree=worktree,
3161 remote=RemoteSpec('origin'),
3162 relpath='.repo/%s' % name,
3163 revisionExpr='refs/heads/master',
3164 revisionId=None,
3165 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003166
3167 def PreSync(self):
3168 if self.Exists:
3169 cb = self.CurrentBranch
3170 if cb:
3171 base = self.GetBranch(cb).merge
3172 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003173 self.revisionExpr = base
3174 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003175
Martin Kelly224a31a2017-07-10 14:46:25 -07003176 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003177 """ Prepare MetaProject for manifest branch switch
3178 """
3179
3180 # detach and delete manifest branch, allowing a new
3181 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003182 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003183 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003184 syncbuf.Finish()
3185
3186 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187 ['update-ref', '-d', 'refs/heads/default'],
3188 capture_stdout=True,
3189 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003190
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003191 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003192 def LastFetch(self):
3193 try:
3194 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3195 return os.path.getmtime(fh)
3196 except OSError:
3197 return 0
3198
3199 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003200 def HasChanges(self):
3201 """Has the remote received new commits not yet checked out?
3202 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003203 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003204 return False
3205
David Pursehouse8a68ff92012-09-24 12:15:13 +09003206 all_refs = self.bare_ref.all
3207 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003208 head = self.work_git.GetHead()
3209 if head.startswith(R_HEADS):
3210 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003211 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003212 except KeyError:
3213 head = None
3214
3215 if revid == head:
3216 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003217 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003218 return True
3219 return False