blob: 5ffa5424fafedbf74a3e36873f2ad568a46105df [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)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001589
1590 # See if we can perform a fast forward merge. This can happen if our
1591 # branch isn't in the exact same state as we last published.
1592 try:
1593 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1594 # Skip the published logic.
1595 pub = False
1596 except GitError:
1597 pub = self.WasPublished(branch.name, all_refs)
1598
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001600 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001601 if not_merged:
1602 if upstream_gain:
1603 # The user has published this branch and some of those
1604 # commits are not yet merged upstream. We do not want
1605 # to rewrite the published commits so we punt.
1606 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001607 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001608 "branch %s is published (but not merged) and is now "
1609 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001610 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001611 elif pub == head:
1612 # All published commits are merged, and thus we are a
1613 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001614 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001615 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001616 if submodules:
1617 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001618 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001619
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001620 # Examine the local commits not in the remote. Find the
1621 # last one attributed to this user, if any.
1622 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001623 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001624 last_mine = None
1625 cnt_mine = 0
1626 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001627 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001628 if committer_email == self.UserEmail:
1629 last_mine = commit_id
1630 cnt_mine += 1
1631
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001632 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001633 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001634
1635 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001636 syncbuf.fail(self, _DirtyError())
1637 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001638
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001639 # If the upstream switched on us, warn the user.
1640 #
1641 if branch.merge != self.revisionExpr:
1642 if branch.merge and self.revisionExpr:
1643 syncbuf.info(self,
1644 'manifest switched %s...%s',
1645 branch.merge,
1646 self.revisionExpr)
1647 elif branch.merge:
1648 syncbuf.info(self,
1649 'manifest no longer tracks %s',
1650 branch.merge)
1651
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001652 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001653 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001654 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001655 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001656 syncbuf.info(self,
1657 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001658 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001660 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001661 if not ID_RE.match(self.revisionExpr):
1662 # in case of manifest sync the revisionExpr might be a SHA1
1663 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001664 if not branch.merge.startswith('refs/'):
1665 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001666 branch.Save()
1667
Mike Pontillod3153822012-02-28 11:53:24 -08001668 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001669 def _docopyandlink():
1670 self._CopyAndLinkFiles()
1671
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001672 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001673 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001674 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001675 if submodules:
1676 syncbuf.later2(self, _dosubmodules)
1677 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001678 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001679 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001680 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001681 if submodules:
1682 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001683 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001684 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001685 syncbuf.fail(self, e)
1686 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001688 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001689 if submodules:
1690 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001692 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693 # dest should already be an absolute path, but src is project relative
1694 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001695 abssrc = os.path.join(self.worktree, src)
1696 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001698 def AddLinkFile(self, src, dest, absdest):
1699 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001700 # make src relative path to dest
1701 absdestdir = os.path.dirname(absdest)
1702 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001703 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001704
James W. Mills24c13082012-04-12 15:04:13 -05001705 def AddAnnotation(self, name, value, keep):
1706 self.annotations.append(_Annotation(name, value, keep))
1707
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001708 def DownloadPatchSet(self, change_id, patch_id):
1709 """Download a single patch set of a single change to FETCH_HEAD.
1710 """
1711 remote = self.GetRemote(self.remote.name)
1712
1713 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001714 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001715 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001716 if GitCommand(self, cmd, bare=True).Wait() != 0:
1717 return None
1718 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001719 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001720 change_id,
1721 patch_id,
1722 self.bare_git.rev_parse('FETCH_HEAD'))
1723
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001725# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001727 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 """Create a new branch off the manifest's revision.
1729 """
Simran Basib9a1b732015-08-20 12:19:28 -07001730 if not branch_merge:
1731 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001732 head = self.work_git.GetHead()
1733 if head == (R_HEADS + name):
1734 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735
David Pursehouse8a68ff92012-09-24 12:15:13 +09001736 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001737 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001738 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001739 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001740 capture_stdout=True,
1741 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001742
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001743 branch = self.GetBranch(name)
1744 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001745 branch.merge = branch_merge
1746 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1747 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001748
1749 if revision is None:
1750 revid = self.GetRevisionId(all_refs)
1751 else:
1752 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001753
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001754 if head.startswith(R_HEADS):
1755 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001756 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001757 except KeyError:
1758 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001759 if revid and head and revid == head:
1760 ref = os.path.join(self.gitdir, R_HEADS + name)
1761 try:
1762 os.makedirs(os.path.dirname(ref))
1763 except OSError:
1764 pass
1765 _lwrite(ref, '%s\n' % revid)
1766 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1767 'ref: %s%s\n' % (R_HEADS, name))
1768 branch.Save()
1769 return True
1770
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001771 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001772 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001773 capture_stdout=True,
1774 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001775 branch.Save()
1776 return True
1777 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778
Wink Saville02d79452009-04-10 13:01:24 -07001779 def CheckoutBranch(self, name):
1780 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001781
1782 Args:
1783 name: The name of the branch to checkout.
1784
1785 Returns:
1786 True if the checkout succeeded; False if it didn't; None if the branch
1787 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001788 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001789 rev = R_HEADS + name
1790 head = self.work_git.GetHead()
1791 if head == rev:
1792 # Already on the branch
1793 #
1794 return True
Wink Saville02d79452009-04-10 13:01:24 -07001795
David Pursehouse8a68ff92012-09-24 12:15:13 +09001796 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001797 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001798 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001799 except KeyError:
1800 # Branch does not exist in this project
1801 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001802 return None
Wink Saville02d79452009-04-10 13:01:24 -07001803
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001804 if head.startswith(R_HEADS):
1805 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001806 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001807 except KeyError:
1808 head = None
1809
1810 if head == revid:
1811 # Same revision; just update HEAD to point to the new
1812 # target branch, but otherwise take no other action.
1813 #
1814 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1815 'ref: %s%s\n' % (R_HEADS, name))
1816 return True
1817
1818 return GitCommand(self,
1819 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001820 capture_stdout=True,
1821 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001822
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001823 def AbandonBranch(self, name):
1824 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001825
1826 Args:
1827 name: The name of the branch to abandon.
1828
1829 Returns:
1830 True if the abandon succeeded; False if it didn't; None if the branch
1831 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001832 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001833 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001834 all_refs = self.bare_ref.all
1835 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001836 # Doesn't exist
1837 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001838
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001839 head = self.work_git.GetHead()
1840 if head == rev:
1841 # We can't destroy the branch while we are sitting
1842 # on it. Switch to a detached HEAD.
1843 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001844 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001845
David Pursehouse8a68ff92012-09-24 12:15:13 +09001846 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001847 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001848 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1849 '%s\n' % revid)
1850 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001851 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001852
1853 return GitCommand(self,
1854 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001855 capture_stdout=True,
1856 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001857
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001858 def PruneHeads(self):
1859 """Prune any topic branches already merged into upstream.
1860 """
1861 cb = self.CurrentBranch
1862 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001863 left = self._allrefs
1864 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001865 if name.startswith(R_HEADS):
1866 name = name[len(R_HEADS):]
1867 if cb is None or name != cb:
1868 kill.append(name)
1869
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001870 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001871 if cb is not None \
1872 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001873 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001874 self.work_git.DetachHead(HEAD)
1875 kill.append(cb)
1876
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001877 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001878 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001879
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001880 try:
1881 self.bare_git.DetachHead(rev)
1882
1883 b = ['branch', '-d']
1884 b.extend(kill)
1885 b = GitCommand(self, b, bare=True,
1886 capture_stdout=True,
1887 capture_stderr=True)
1888 b.Wait()
1889 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001890 if ID_RE.match(old):
1891 self.bare_git.DetachHead(old)
1892 else:
1893 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001894 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001895
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001896 for branch in kill:
1897 if (R_HEADS + branch) not in left:
1898 self.CleanPublishedCache()
1899 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001900
1901 if cb and cb not in kill:
1902 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001903 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001904
1905 kept = []
1906 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001907 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001908 branch = self.GetBranch(branch)
1909 base = branch.LocalMerge
1910 if not base:
1911 base = rev
1912 kept.append(ReviewableBranch(self, branch, base))
1913 return kept
1914
1915
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001916# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001917
1918 def GetRegisteredSubprojects(self):
1919 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001920
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001921 def rec(subprojects):
1922 if not subprojects:
1923 return
1924 result.extend(subprojects)
1925 for p in subprojects:
1926 rec(p.subprojects)
1927 rec(self.subprojects)
1928 return result
1929
1930 def _GetSubmodules(self):
1931 # Unfortunately we cannot call `git submodule status --recursive` here
1932 # because the working tree might not exist yet, and it cannot be used
1933 # without a working tree in its current implementation.
1934
1935 def get_submodules(gitdir, rev):
1936 # Parse .gitmodules for submodule sub_paths and sub_urls
1937 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1938 if not sub_paths:
1939 return []
1940 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1941 # revision of submodule repository
1942 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1943 submodules = []
1944 for sub_path, sub_url in zip(sub_paths, sub_urls):
1945 try:
1946 sub_rev = sub_revs[sub_path]
1947 except KeyError:
1948 # Ignore non-exist submodules
1949 continue
1950 submodules.append((sub_rev, sub_path, sub_url))
1951 return submodules
1952
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001953 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1954 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001955
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001956 def parse_gitmodules(gitdir, rev):
1957 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1958 try:
Anthony King7bdac712014-07-16 12:56:40 +01001959 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1960 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001961 except GitError:
1962 return [], []
1963 if p.Wait() != 0:
1964 return [], []
1965
1966 gitmodules_lines = []
1967 fd, temp_gitmodules_path = tempfile.mkstemp()
1968 try:
1969 os.write(fd, p.stdout)
1970 os.close(fd)
1971 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001972 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1973 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001974 if p.Wait() != 0:
1975 return [], []
1976 gitmodules_lines = p.stdout.split('\n')
1977 except GitError:
1978 return [], []
1979 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001980 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001981
1982 names = set()
1983 paths = {}
1984 urls = {}
1985 for line in gitmodules_lines:
1986 if not line:
1987 continue
1988 m = re_path.match(line)
1989 if m:
1990 names.add(m.group(1))
1991 paths[m.group(1)] = m.group(2)
1992 continue
1993 m = re_url.match(line)
1994 if m:
1995 names.add(m.group(1))
1996 urls[m.group(1)] = m.group(2)
1997 continue
1998 names = sorted(names)
1999 return ([paths.get(name, '') for name in names],
2000 [urls.get(name, '') for name in names])
2001
2002 def git_ls_tree(gitdir, rev, paths):
2003 cmd = ['ls-tree', rev, '--']
2004 cmd.extend(paths)
2005 try:
Anthony King7bdac712014-07-16 12:56:40 +01002006 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2007 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002008 except GitError:
2009 return []
2010 if p.Wait() != 0:
2011 return []
2012 objects = {}
2013 for line in p.stdout.split('\n'):
2014 if not line.strip():
2015 continue
2016 object_rev, object_path = line.split()[2:4]
2017 objects[object_path] = object_rev
2018 return objects
2019
2020 try:
2021 rev = self.GetRevisionId()
2022 except GitError:
2023 return []
2024 return get_submodules(self.gitdir, rev)
2025
2026 def GetDerivedSubprojects(self):
2027 result = []
2028 if not self.Exists:
2029 # If git repo does not exist yet, querying its submodules will
2030 # mess up its states; so return here.
2031 return result
2032 for rev, path, url in self._GetSubmodules():
2033 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002034 relpath, worktree, gitdir, objdir = \
2035 self.manifest.GetSubprojectPaths(self, name, path)
2036 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002037 if project:
2038 result.extend(project.GetDerivedSubprojects())
2039 continue
David James8d201162013-10-11 17:03:19 -07002040
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002041 if url.startswith('..'):
2042 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002043 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002044 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002045 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002046 review=self.remote.review,
2047 revision=self.remote.revision)
2048 subproject = Project(manifest=self.manifest,
2049 name=name,
2050 remote=remote,
2051 gitdir=gitdir,
2052 objdir=objdir,
2053 worktree=worktree,
2054 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002055 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002056 revisionId=rev,
2057 rebase=self.rebase,
2058 groups=self.groups,
2059 sync_c=self.sync_c,
2060 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002061 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002062 parent=self,
2063 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002064 result.append(subproject)
2065 result.extend(subproject.GetDerivedSubprojects())
2066 return result
2067
2068
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002069# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002070 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002071 try:
2072 # if revision (sha or tag) is not present then following function
2073 # throws an error.
2074 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2075 return True
2076 except GitError:
2077 # There is no such persistent revision. We have to fetch it.
2078 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002079
Julien Campergue335f5ef2013-10-16 11:02:35 +02002080 def _FetchArchive(self, tarpath, cwd=None):
2081 cmd = ['archive', '-v', '-o', tarpath]
2082 cmd.append('--remote=%s' % self.remote.url)
2083 cmd.append('--prefix=%s/' % self.relpath)
2084 cmd.append(self.revisionExpr)
2085
2086 command = GitCommand(self, cmd, cwd=cwd,
2087 capture_stdout=True,
2088 capture_stderr=True)
2089
2090 if command.Wait() != 0:
2091 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2092
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002093 def _RemoteFetch(self, name=None,
2094 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002095 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002096 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002097 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002098 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002099 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002100 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002101 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002102 force_sync=False,
2103 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002104
2105 is_sha1 = False
2106 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002107 # The depth should not be used when fetching to a mirror because
2108 # it will result in a shallow repository that cannot be cloned or
2109 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002110 # The repo project should also never be synced with partial depth.
2111 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2112 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002113
Shawn Pearce69e04d82014-01-29 12:48:54 -08002114 if depth:
2115 current_branch_only = True
2116
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002117 if ID_RE.match(self.revisionExpr) is not None:
2118 is_sha1 = True
2119
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002120 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002121 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002122 # this is a tag and its sha1 value should never change
2123 tag_name = self.revisionExpr[len(R_TAGS):]
2124
2125 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002126 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002127 if not quiet:
2128 print('Skipped fetching project %s (already have persistent ref)'
2129 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002130 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002131 if is_sha1 and not depth:
2132 # When syncing a specific commit and --depth is not set:
2133 # * if upstream is explicitly specified and is not a sha1, fetch only
2134 # upstream as users expect only upstream to be fetch.
2135 # Note: The commit might not be in upstream in which case the sync
2136 # will fail.
2137 # * otherwise, fetch all branches to make sure we end up with the
2138 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002139 if self.upstream:
2140 current_branch_only = not ID_RE.match(self.upstream)
2141 else:
2142 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002143
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 if not name:
2145 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002146
2147 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002148 remote = self.GetRemote(name)
2149 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002150 ssh_proxy = True
2151
Shawn O. Pearce88443382010-10-08 10:02:09 +02002152 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002153 if alt_dir and 'objects' == os.path.basename(alt_dir):
2154 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002155 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2156 remote = self.GetRemote(name)
2157
David Pursehouse8a68ff92012-09-24 12:15:13 +09002158 all_refs = self.bare_ref.all
2159 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002160 tmp = set()
2161
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302162 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002163 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002165 all_refs[r] = ref_id
2166 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002167 continue
2168
David Pursehouse8a68ff92012-09-24 12:15:13 +09002169 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002170 continue
2171
David Pursehouse8a68ff92012-09-24 12:15:13 +09002172 r = 'refs/_alt/%s' % ref_id
2173 all_refs[r] = ref_id
2174 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002175 tmp.add(r)
2176
heping3d7bbc92017-04-12 19:51:47 +08002177 tmp_packed_lines = []
2178 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002179
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302180 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002181 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002182 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002183 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002184 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002185
heping3d7bbc92017-04-12 19:51:47 +08002186 tmp_packed = ''.join(tmp_packed_lines)
2187 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002188 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002189 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002190 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002191
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002192 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002193
Xin Li745be2e2019-06-03 11:24:30 -07002194 if clone_filter:
2195 git_require((2, 19, 0), fail=True, msg='partial clones')
2196 cmd.append('--filter=%s' % clone_filter)
2197 self.config.SetString('extensions.partialclone', self.remote.name)
2198
Conley Owensf97e8382015-01-21 11:12:46 -08002199 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002200 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002201 else:
2202 # If this repo has shallow objects, then we don't know which refs have
2203 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2204 # do this with projects that don't have shallow objects, since it is less
2205 # efficient.
2206 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2207 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002208
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002209 if quiet:
2210 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002211 if not self.worktree:
2212 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002213 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002214
Xin Lia2cd6ae2019-09-16 10:55:41 -07002215 spec = []
2216
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002217 # If using depth then we should not get all the tags since they may
2218 # be outside of the depth.
2219 if no_tags or depth:
2220 cmd.append('--no-tags')
2221 else:
2222 cmd.append('--tags')
Xin Lia2cd6ae2019-09-16 10:55:41 -07002223 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002224
Mike Frysingere57f1142019-03-18 21:27:54 -04002225 if force_sync:
2226 cmd.append('--force')
2227
David Pursehouse74cfd272015-10-14 10:50:15 +09002228 if prune:
2229 cmd.append('--prune')
2230
Martin Kellye4e94d22017-03-21 16:05:12 -07002231 if submodules:
2232 cmd.append('--recurse-submodules=on-demand')
2233
Brian Harring14a66742012-09-28 20:21:57 -07002234 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002235 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002236 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002237 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002238 spec.append('tag')
2239 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002240
David Pursehouse403b64e2015-04-27 10:41:33 +09002241 if not self.manifest.IsMirror:
2242 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002243 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002244 # Shallow checkout of a specific commit, fetch from that commit and not
2245 # the heads only as the commit might be deeper in the history.
2246 spec.append(branch)
2247 else:
2248 if is_sha1:
2249 branch = self.upstream
2250 if branch is not None and branch.strip():
2251 if not branch.startswith('refs/'):
2252 branch = R_HEADS + branch
2253 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002254 cmd.extend(spec)
2255
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002256 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002257 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002258 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002259 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002260 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002261 ok = True
2262 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002263 # If needed, run the 'git remote prune' the first time through the loop
2264 elif (not _i and
2265 "error:" in gitcmd.stderr and
2266 "git remote prune" in gitcmd.stderr):
2267 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002268 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002269 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002270 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002271 break
2272 continue
Brian Harring14a66742012-09-28 20:21:57 -07002273 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002274 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2275 # in sha1 mode, we just tried sync'ing from the upstream field; it
2276 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002277 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002278 elif ret < 0:
2279 # Git died with a signal, exit immediately
2280 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002281 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002282
2283 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002284 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002285 if old_packed != '':
2286 _lwrite(packed_refs, old_packed)
2287 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002288 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002289 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002290
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002291 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002292 # We just synced the upstream given branch; verify we
2293 # got what we wanted, else trigger a second run of all
2294 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002295 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002296 if current_branch_only and depth:
2297 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002298 return self._RemoteFetch(name=name,
2299 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002300 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002301 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002302 else:
2303 # Avoid infinite recursion: sync all branches with depth set to None
2304 return self._RemoteFetch(name=name, current_branch_only=False,
2305 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002306 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002307
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002308 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002309
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002310 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002311 if initial and \
2312 (self.manifest.manifestProject.config.GetString('repo.depth') or
2313 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002314 return False
2315
2316 remote = self.GetRemote(self.remote.name)
2317 bundle_url = remote.url + '/clone.bundle'
2318 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002319 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2320 'persistent-http',
2321 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002322 return False
2323
2324 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2325 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2326
2327 exist_dst = os.path.exists(bundle_dst)
2328 exist_tmp = os.path.exists(bundle_tmp)
2329
2330 if not initial and not exist_dst and not exist_tmp:
2331 return False
2332
2333 if not exist_dst:
2334 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2335 if not exist_dst:
2336 return False
2337
2338 cmd = ['fetch']
2339 if quiet:
2340 cmd.append('--quiet')
2341 if not self.worktree:
2342 cmd.append('--update-head-ok')
2343 cmd.append(bundle_dst)
2344 for f in remote.fetch:
2345 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002346 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002347
2348 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002349 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002350 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002351 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002352 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002353 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002355 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002356 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002357 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002358
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002359 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002360 if quiet:
2361 cmd += ['--silent']
2362 if os.path.exists(tmpPath):
2363 size = os.stat(tmpPath).st_size
2364 if size >= 1024:
2365 cmd += ['--continue-at', '%d' % (size,)]
2366 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002367 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002368 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002369 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002370 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002371 if proxy:
2372 cmd += ['--proxy', proxy]
2373 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2374 cmd += ['--proxy', os.environ['http_proxy']]
2375 if srcUrl.startswith('persistent-https'):
2376 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2377 elif srcUrl.startswith('persistent-http'):
2378 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002379 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002380
Dave Borowitz137d0132015-01-02 11:12:54 -08002381 if IsTrace():
2382 Trace('%s', ' '.join(cmd))
2383 try:
2384 proc = subprocess.Popen(cmd)
2385 except OSError:
2386 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002387
Dave Borowitz137d0132015-01-02 11:12:54 -08002388 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002389
Dave Borowitz137d0132015-01-02 11:12:54 -08002390 if curlret == 22:
2391 # From curl man page:
2392 # 22: HTTP page not retrieved. The requested url was not found or
2393 # returned another error with the HTTP error code being 400 or above.
2394 # This return code only appears if -f, --fail is used.
2395 if not quiet:
2396 print("Server does not provide clone.bundle; ignoring.",
2397 file=sys.stderr)
2398 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002399
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002400 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002401 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002402 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002403 return True
2404 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002405 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002406 return False
2407 else:
2408 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002409
Kris Giesingc8d882a2014-12-23 13:02:32 -08002410 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002411 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002412 with open(path, 'rb') as f:
2413 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002414 return True
2415 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002416 if not quiet:
2417 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002418 return False
2419 except OSError:
2420 return False
2421
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002422 def _Checkout(self, rev, quiet=False):
2423 cmd = ['checkout']
2424 if quiet:
2425 cmd.append('-q')
2426 cmd.append(rev)
2427 cmd.append('--')
2428 if GitCommand(self, cmd).Wait() != 0:
2429 if self._allrefs:
2430 raise GitError('%s checkout %s ' % (self.name, rev))
2431
Anthony King7bdac712014-07-16 12:56:40 +01002432 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002433 cmd = ['cherry-pick']
2434 cmd.append(rev)
2435 cmd.append('--')
2436 if GitCommand(self, cmd).Wait() != 0:
2437 if self._allrefs:
2438 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2439
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302440 def _LsRemote(self, refs):
2441 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302442 p = GitCommand(self, cmd, capture_stdout=True)
2443 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002444 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302445 return None
2446
Anthony King7bdac712014-07-16 12:56:40 +01002447 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002448 cmd = ['revert']
2449 cmd.append('--no-edit')
2450 cmd.append(rev)
2451 cmd.append('--')
2452 if GitCommand(self, cmd).Wait() != 0:
2453 if self._allrefs:
2454 raise GitError('%s revert %s ' % (self.name, rev))
2455
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456 def _ResetHard(self, rev, quiet=True):
2457 cmd = ['reset', '--hard']
2458 if quiet:
2459 cmd.append('-q')
2460 cmd.append(rev)
2461 if GitCommand(self, cmd).Wait() != 0:
2462 raise GitError('%s reset --hard %s ' % (self.name, rev))
2463
Martin Kellye4e94d22017-03-21 16:05:12 -07002464 def _SyncSubmodules(self, quiet=True):
2465 cmd = ['submodule', 'update', '--init', '--recursive']
2466 if quiet:
2467 cmd.append('-q')
2468 if GitCommand(self, cmd).Wait() != 0:
2469 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2470
Anthony King7bdac712014-07-16 12:56:40 +01002471 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002472 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002473 if onto is not None:
2474 cmd.extend(['--onto', onto])
2475 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002476 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477 raise GitError('%s rebase %s ' % (self.name, upstream))
2478
Pierre Tardy3d125942012-05-04 12:18:12 +02002479 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002481 if ffonly:
2482 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002483 if GitCommand(self, cmd).Wait() != 0:
2484 raise GitError('%s merge %s ' % (self.name, head))
2485
Kevin Degiabaa7f32014-11-12 11:27:45 -07002486 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002487 init_git_dir = not os.path.exists(self.gitdir)
2488 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002489 try:
2490 # Initialize the bare repository, which contains all of the objects.
2491 if init_obj_dir:
2492 os.makedirs(self.objdir)
2493 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002494
Kevin Degib1a07b82015-07-27 13:33:43 -06002495 # If we have a separate directory to hold refs, initialize it as well.
2496 if self.objdir != self.gitdir:
2497 if init_git_dir:
2498 os.makedirs(self.gitdir)
2499
2500 if init_obj_dir or init_git_dir:
2501 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2502 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002503 try:
2504 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2505 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002506 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002507 print("Retrying clone after deleting %s" %
2508 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002509 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002510 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2511 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002512 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002513 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002514 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2515 except:
2516 raise e
2517 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002518
Kevin Degi384b3c52014-10-16 16:02:58 -06002519 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002520 mp = self.manifest.manifestProject
2521 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002522
Kevin Degib1a07b82015-07-27 13:33:43 -06002523 if ref_dir or mirror_git:
2524 if not mirror_git:
2525 mirror_git = os.path.join(ref_dir, self.name + '.git')
2526 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2527 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002528
Kevin Degib1a07b82015-07-27 13:33:43 -06002529 if os.path.exists(mirror_git):
2530 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002531
Kevin Degib1a07b82015-07-27 13:33:43 -06002532 elif os.path.exists(repo_git):
2533 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002534
Kevin Degib1a07b82015-07-27 13:33:43 -06002535 else:
2536 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002537
Kevin Degib1a07b82015-07-27 13:33:43 -06002538 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002539 if not os.path.isabs(ref_dir):
2540 # The alternate directory is relative to the object database.
2541 ref_dir = os.path.relpath(ref_dir,
2542 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002543 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2544 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002545
Kevin Degib1a07b82015-07-27 13:33:43 -06002546 self._UpdateHooks()
2547
2548 m = self.manifest.manifestProject.config
2549 for key in ['user.name', 'user.email']:
2550 if m.Has(key, include_defaults=False):
2551 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002552 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002553 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002554 if self.manifest.IsMirror:
2555 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002556 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002557 self.config.SetString('core.bare', None)
2558 except Exception:
2559 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002560 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002561 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002562 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002563 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002564
Jimmie Westera0444582012-10-24 13:44:42 +02002565 def _UpdateHooks(self):
2566 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002567 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002568
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002569 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002570 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002571 if not os.path.exists(hooks):
2572 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002573 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002574 name = os.path.basename(stock_hook)
2575
Victor Boivie65e0f352011-04-18 11:23:29 +02002576 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002577 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002578 # Don't install a Gerrit Code Review hook if this
2579 # project does not appear to use it for reviews.
2580 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002581 # Since the manifest project is one of those, but also
2582 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002583 continue
2584
2585 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002586 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002587 continue
2588 if os.path.exists(dst):
2589 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002590 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002591 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002592 _warn("%s: Not replacing locally modified %s hook",
2593 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002594 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002595 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002596 platform_utils.symlink(
2597 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002598 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002599 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002600 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002601 else:
2602 raise
2603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002604 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002605 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002607 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002608 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002609 remote.review = self.remote.review
2610 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002612 if self.worktree:
2613 remote.ResetFetch(mirror=False)
2614 else:
2615 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 remote.Save()
2617
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002618 def _InitMRef(self):
2619 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002620 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002621
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002622 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002623 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002624
2625 def _InitAnyMRef(self, ref):
2626 cur = self.bare_ref.symref(ref)
2627
2628 if self.revisionId:
2629 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2630 msg = 'manifest set to %s' % self.revisionId
2631 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002632 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002633 else:
2634 remote = self.GetRemote(self.remote.name)
2635 dst = remote.ToLocal(self.revisionExpr)
2636 if cur != dst:
2637 msg = 'manifest set to %s' % self.revisionExpr
2638 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002639
Kevin Degi384b3c52014-10-16 16:02:58 -06002640 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002641 symlink_files = self.shareable_files[:]
2642 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002643 if share_refs:
2644 symlink_files += self.working_tree_files
2645 symlink_dirs += self.working_tree_dirs
2646 to_symlink = symlink_files + symlink_dirs
2647 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002648 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002649 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002650 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002651 # Fail if the links are pointing to the wrong place
2652 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002653 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002654 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002655 'work tree. If you\'re comfortable with the '
2656 'possibility of losing the work tree\'s git metadata,'
2657 ' use `repo sync --force-sync {0}` to '
2658 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002659
David James8d201162013-10-11 17:03:19 -07002660 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2661 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2662
2663 Args:
2664 gitdir: The bare git repository. Must already be initialized.
2665 dotgit: The repository you would like to initialize.
2666 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2667 Only one work tree can store refs under a given |gitdir|.
2668 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2669 This saves you the effort of initializing |dotgit| yourself.
2670 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002671 symlink_files = self.shareable_files[:]
2672 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002673 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002674 symlink_files += self.working_tree_files
2675 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002676 to_symlink = symlink_files + symlink_dirs
2677
2678 to_copy = []
2679 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002680 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002681
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002682 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002683 for name in set(to_copy).union(to_symlink):
2684 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002685 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002686 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002687
Kevin Degi384b3c52014-10-16 16:02:58 -06002688 if os.path.lexists(dst):
2689 continue
David James8d201162013-10-11 17:03:19 -07002690
2691 # If the source dir doesn't exist, create an empty dir.
2692 if name in symlink_dirs and not os.path.lexists(src):
2693 os.makedirs(src)
2694
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002695 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002696 platform_utils.symlink(
2697 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002698 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002699 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002700 shutil.copytree(src, dst)
2701 elif os.path.isfile(src):
2702 shutil.copy(src, dst)
2703
Conley Owens80b87fe2014-05-09 17:13:44 -07002704 # If the source file doesn't exist, ensure the destination
2705 # file doesn't either.
2706 if name in symlink_files and not os.path.lexists(src):
2707 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002708 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002709 except OSError:
2710 pass
2711
David James8d201162013-10-11 17:03:19 -07002712 except OSError as e:
2713 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002714 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002715 else:
2716 raise
2717
Martin Kellye4e94d22017-03-21 16:05:12 -07002718 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002719 realdotgit = os.path.join(self.worktree, '.git')
2720 tmpdotgit = realdotgit + '.tmp'
2721 init_dotgit = not os.path.exists(realdotgit)
2722 if init_dotgit:
2723 dotgit = tmpdotgit
2724 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2725 os.makedirs(tmpdotgit)
2726 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2727 copy_all=False)
2728 else:
2729 dotgit = realdotgit
2730
Kevin Degib1a07b82015-07-27 13:33:43 -06002731 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002732 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2733 except GitError as e:
2734 if force_sync and not init_dotgit:
2735 try:
2736 platform_utils.rmtree(dotgit)
2737 return self._InitWorkTree(force_sync=False, submodules=submodules)
2738 except:
2739 raise e
2740 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002741
Mike Frysingerf4545122019-11-11 04:34:16 -05002742 if init_dotgit:
2743 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002744
Mike Frysingerf4545122019-11-11 04:34:16 -05002745 # Now that the .git dir is fully set up, move it to its final home.
2746 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002747
Mike Frysingerf4545122019-11-11 04:34:16 -05002748 # Finish checking out the worktree.
2749 cmd = ['read-tree', '--reset', '-u']
2750 cmd.append('-v')
2751 cmd.append(HEAD)
2752 if GitCommand(self, cmd).Wait() != 0:
2753 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002754
Mike Frysingerf4545122019-11-11 04:34:16 -05002755 if submodules:
2756 self._SyncSubmodules(quiet=True)
2757 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002758
Renaud Paquay788e9622017-01-27 11:41:12 -08002759 def _get_symlink_error_message(self):
2760 if platform_utils.isWindows():
2761 return ('Unable to create symbolic link. Please re-run the command as '
2762 'Administrator, or see '
2763 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2764 'for other options.')
2765 return 'filesystem must support symlinks'
2766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002768 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002769
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002770 def _revlist(self, *args, **kw):
2771 a = []
2772 a.extend(args)
2773 a.append('--')
2774 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002775
2776 @property
2777 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002778 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002779
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002780 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002781 """Get logs between two revisions of this project."""
2782 comp = '..'
2783 if rev1:
2784 revs = [rev1]
2785 if rev2:
2786 revs.extend([comp, rev2])
2787 cmd = ['log', ''.join(revs)]
2788 out = DiffColoring(self.config)
2789 if out.is_on and color:
2790 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002791 if pretty_format is not None:
2792 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002793 if oneline:
2794 cmd.append('--oneline')
2795
2796 try:
2797 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2798 if log.Wait() == 0:
2799 return log.stdout
2800 except GitError:
2801 # worktree may not exist if groups changed for example. In that case,
2802 # try in gitdir instead.
2803 if not os.path.exists(self.worktree):
2804 return self.bare_git.log(*cmd[1:])
2805 else:
2806 raise
2807 return None
2808
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002809 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2810 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002811 """Get the list of logs from this revision to given revisionId"""
2812 logs = {}
2813 selfId = self.GetRevisionId(self._allrefs)
2814 toId = toProject.GetRevisionId(toProject._allrefs)
2815
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002816 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2817 pretty_format=pretty_format)
2818 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2819 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002820 return logs
2821
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002822 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002823
David James8d201162013-10-11 17:03:19 -07002824 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002825 self._project = project
2826 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002827 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829 def LsOthers(self):
2830 p = GitCommand(self._project,
2831 ['ls-files',
2832 '-z',
2833 '--others',
2834 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002835 bare=False,
David James8d201162013-10-11 17:03:19 -07002836 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002837 capture_stdout=True,
2838 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002839 if p.Wait() == 0:
2840 out = p.stdout
2841 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002842 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002843 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002844 return []
2845
2846 def DiffZ(self, name, *args):
2847 cmd = [name]
2848 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002849 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002850 cmd.extend(args)
2851 p = GitCommand(self._project,
2852 cmd,
David James8d201162013-10-11 17:03:19 -07002853 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002854 bare=False,
2855 capture_stdout=True,
2856 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002857 try:
2858 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002859 if not hasattr(out, 'encode'):
2860 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002861 r = {}
2862 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002863 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002864 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002865 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002866 info = next(out)
2867 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002868 except StopIteration:
2869 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002870
2871 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002872
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002873 def __init__(self, path, omode, nmode, oid, nid, state):
2874 self.path = path
2875 self.src_path = None
2876 self.old_mode = omode
2877 self.new_mode = nmode
2878 self.old_id = oid
2879 self.new_id = nid
2880
2881 if len(state) == 1:
2882 self.status = state
2883 self.level = None
2884 else:
2885 self.status = state[:1]
2886 self.level = state[1:]
2887 while self.level.startswith('0'):
2888 self.level = self.level[1:]
2889
2890 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002891 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002892 if info.status in ('R', 'C'):
2893 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002894 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002895 r[info.path] = info
2896 return r
2897 finally:
2898 p.Wait()
2899
2900 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002901 if self._bare:
2902 path = os.path.join(self._project.gitdir, HEAD)
2903 else:
2904 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002905 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002906 with open(path) as fd:
2907 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002908 except IOError as e:
2909 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002910 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302911 line = line.decode()
2912 except AttributeError:
2913 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002914 if line.startswith('ref: '):
2915 return line[5:-1]
2916 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002917
2918 def SetHead(self, ref, message=None):
2919 cmdv = []
2920 if message is not None:
2921 cmdv.extend(['-m', message])
2922 cmdv.append(HEAD)
2923 cmdv.append(ref)
2924 self.symbolic_ref(*cmdv)
2925
2926 def DetachHead(self, new, message=None):
2927 cmdv = ['--no-deref']
2928 if message is not None:
2929 cmdv.extend(['-m', message])
2930 cmdv.append(HEAD)
2931 cmdv.append(new)
2932 self.update_ref(*cmdv)
2933
2934 def UpdateRef(self, name, new, old=None,
2935 message=None,
2936 detach=False):
2937 cmdv = []
2938 if message is not None:
2939 cmdv.extend(['-m', message])
2940 if detach:
2941 cmdv.append('--no-deref')
2942 cmdv.append(name)
2943 cmdv.append(new)
2944 if old is not None:
2945 cmdv.append(old)
2946 self.update_ref(*cmdv)
2947
2948 def DeleteRef(self, name, old=None):
2949 if not old:
2950 old = self.rev_parse(name)
2951 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002952 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002953
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002954 def rev_list(self, *args, **kw):
2955 if 'format' in kw:
2956 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2957 else:
2958 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002959 cmdv.extend(args)
2960 p = GitCommand(self._project,
2961 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002962 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002963 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002964 capture_stdout=True,
2965 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002967 raise GitError('%s rev-list %s: %s' %
2968 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002969 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002970
2971 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002972 """Allow arbitrary git commands using pythonic syntax.
2973
2974 This allows you to do things like:
2975 git_obj.rev_parse('HEAD')
2976
2977 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2978 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002979 Any other positional arguments will be passed to the git command, and the
2980 following keyword arguments are supported:
2981 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002982
2983 Args:
2984 name: The name of the git command to call. Any '_' characters will
2985 be replaced with '-'.
2986
2987 Returns:
2988 A callable object that will try to call git with the named command.
2989 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002990 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002991
Dave Borowitz091f8932012-10-23 17:01:04 -07002992 def runner(*args, **kwargs):
2993 cmdv = []
2994 config = kwargs.pop('config', None)
2995 for k in kwargs:
2996 raise TypeError('%s() got an unexpected keyword argument %r'
2997 % (name, k))
2998 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002999 if not git_require((1, 7, 2)):
3000 raise ValueError('cannot set config on command line for %s()'
3001 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303002 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003003 cmdv.append('-c')
3004 cmdv.append('%s=%s' % (k, v))
3005 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003006 cmdv.extend(args)
3007 p = GitCommand(self._project,
3008 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003009 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003010 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003011 capture_stdout=True,
3012 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003013 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003014 raise GitError('%s %s: %s' %
3015 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003016 r = p.stdout
3017 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3018 return r[:-1]
3019 return r
3020 return runner
3021
3022
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003023class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003024
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003025 def __str__(self):
3026 return 'prior sync failed; rebase still in progress'
3027
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003028
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003029class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003030
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003031 def __str__(self):
3032 return 'contains uncommitted changes'
3033
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003034
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003035class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003036
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003037 def __init__(self, project, text):
3038 self.project = project
3039 self.text = text
3040
3041 def Print(self, syncbuf):
3042 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3043 syncbuf.out.nl()
3044
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003045
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003046class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003047
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003048 def __init__(self, project, why):
3049 self.project = project
3050 self.why = why
3051
3052 def Print(self, syncbuf):
3053 syncbuf.out.fail('error: %s/: %s',
3054 self.project.relpath,
3055 str(self.why))
3056 syncbuf.out.nl()
3057
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003059class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003060
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003061 def __init__(self, project, action):
3062 self.project = project
3063 self.action = action
3064
3065 def Run(self, syncbuf):
3066 out = syncbuf.out
3067 out.project('project %s/', self.project.relpath)
3068 out.nl()
3069 try:
3070 self.action()
3071 out.nl()
3072 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003073 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003074 out.nl()
3075 return False
3076
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003077
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003078class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003079
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003080 def __init__(self, config):
3081 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003082 self.project = self.printer('header', attr='bold')
3083 self.info = self.printer('info')
3084 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003085
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003086
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003087class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003088
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003089 def __init__(self, config, detach_head=False):
3090 self._messages = []
3091 self._failures = []
3092 self._later_queue1 = []
3093 self._later_queue2 = []
3094
3095 self.out = _SyncColoring(config)
3096 self.out.redirect(sys.stderr)
3097
3098 self.detach_head = detach_head
3099 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003100 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003101
3102 def info(self, project, fmt, *args):
3103 self._messages.append(_InfoMessage(project, fmt % args))
3104
3105 def fail(self, project, err=None):
3106 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003107 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003108
3109 def later1(self, project, what):
3110 self._later_queue1.append(_Later(project, what))
3111
3112 def later2(self, project, what):
3113 self._later_queue2.append(_Later(project, what))
3114
3115 def Finish(self):
3116 self._PrintMessages()
3117 self._RunLater()
3118 self._PrintMessages()
3119 return self.clean
3120
David Rileye0684ad2017-04-05 00:02:59 -07003121 def Recently(self):
3122 recent_clean = self.recent_clean
3123 self.recent_clean = True
3124 return recent_clean
3125
3126 def _MarkUnclean(self):
3127 self.clean = False
3128 self.recent_clean = False
3129
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003130 def _RunLater(self):
3131 for q in ['_later_queue1', '_later_queue2']:
3132 if not self._RunQueue(q):
3133 return
3134
3135 def _RunQueue(self, queue):
3136 for m in getattr(self, queue):
3137 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003138 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003139 return False
3140 setattr(self, queue, [])
3141 return True
3142
3143 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003144 if self._messages or self._failures:
3145 if os.isatty(2):
3146 self.out.write(progress.CSI_ERASE_LINE)
3147 self.out.write('\r')
3148
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003149 for m in self._messages:
3150 m.Print(self)
3151 for m in self._failures:
3152 m.Print(self)
3153
3154 self._messages = []
3155 self._failures = []
3156
3157
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003158class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003159
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003160 """A special project housed under .repo.
3161 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003162
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003163 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003164 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003165 manifest=manifest,
3166 name=name,
3167 gitdir=gitdir,
3168 objdir=gitdir,
3169 worktree=worktree,
3170 remote=RemoteSpec('origin'),
3171 relpath='.repo/%s' % name,
3172 revisionExpr='refs/heads/master',
3173 revisionId=None,
3174 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003175
3176 def PreSync(self):
3177 if self.Exists:
3178 cb = self.CurrentBranch
3179 if cb:
3180 base = self.GetBranch(cb).merge
3181 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003182 self.revisionExpr = base
3183 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003184
Martin Kelly224a31a2017-07-10 14:46:25 -07003185 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003186 """ Prepare MetaProject for manifest branch switch
3187 """
3188
3189 # detach and delete manifest branch, allowing a new
3190 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003191 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003192 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003193 syncbuf.Finish()
3194
3195 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003196 ['update-ref', '-d', 'refs/heads/default'],
3197 capture_stdout=True,
3198 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003199
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003200 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003201 def LastFetch(self):
3202 try:
3203 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3204 return os.path.getmtime(fh)
3205 except OSError:
3206 return 0
3207
3208 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003209 def HasChanges(self):
3210 """Has the remote received new commits not yet checked out?
3211 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003212 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003213 return False
3214
David Pursehouse8a68ff92012-09-24 12:15:13 +09003215 all_refs = self.bare_ref.all
3216 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003217 head = self.work_git.GetHead()
3218 if head.startswith(R_HEADS):
3219 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003220 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003221 except KeyError:
3222 head = None
3223
3224 if revid == head:
3225 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003226 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003227 return True
3228 return False