blob: 6a48c23a618b1c6d45b1572dc2b39fe4482dd9b7 [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 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001502 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001503 all_refs = self.bare_ref.all
1504 self.CleanPublishedCache(all_refs)
1505 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001506
David Pursehouse1d947b32012-10-25 12:23:11 +09001507 def _doff():
1508 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001509 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001510
Martin Kellye4e94d22017-03-21 16:05:12 -07001511 def _dosubmodules():
1512 self._SyncSubmodules(quiet=True)
1513
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001514 head = self.work_git.GetHead()
1515 if head.startswith(R_HEADS):
1516 branch = head[len(R_HEADS):]
1517 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001518 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001519 except KeyError:
1520 head = None
1521 else:
1522 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001524 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001525 # Currently on a detached HEAD. The user is assumed to
1526 # not have any local modifications worth worrying about.
1527 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001528 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001529 syncbuf.fail(self, _PriorSyncFailedError())
1530 return
1531
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001532 if head == revid:
1533 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001534 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001535 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001536 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001537 # The copy/linkfile config may have changed.
1538 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001539 return
1540 else:
1541 lost = self._revlist(not_rev(revid), HEAD)
1542 if lost:
1543 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001545 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001546 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001547 if submodules:
1548 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001549 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001550 syncbuf.fail(self, e)
1551 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001552 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001553 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001555 if head == revid:
1556 # No changes; don't do anything further.
1557 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001558 # The copy/linkfile config may have changed.
1559 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001560 return
1561
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001562 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001564 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001566 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001568 syncbuf.info(self,
1569 "leaving %s; does not track upstream",
1570 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001571 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001572 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001573 if submodules:
1574 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001575 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001576 syncbuf.fail(self, e)
1577 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001578 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001579 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001580
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001581 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001582 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001583 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001584 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585 if not_merged:
1586 if upstream_gain:
1587 # The user has published this branch and some of those
1588 # commits are not yet merged upstream. We do not want
1589 # to rewrite the published commits so we punt.
1590 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001591 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001592 "branch %s is published (but not merged) and is now "
1593 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001594 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001595 elif pub == head:
1596 # All published commits are merged, and thus we are a
1597 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001598 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001599 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001600 if submodules:
1601 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001602 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001604 # Examine the local commits not in the remote. Find the
1605 # last one attributed to this user, if any.
1606 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001607 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001608 last_mine = None
1609 cnt_mine = 0
1610 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001611 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001612 if committer_email == self.UserEmail:
1613 last_mine = commit_id
1614 cnt_mine += 1
1615
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001616 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001617 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001618
1619 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001620 syncbuf.fail(self, _DirtyError())
1621 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001622
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001623 # If the upstream switched on us, warn the user.
1624 #
1625 if branch.merge != self.revisionExpr:
1626 if branch.merge and self.revisionExpr:
1627 syncbuf.info(self,
1628 'manifest switched %s...%s',
1629 branch.merge,
1630 self.revisionExpr)
1631 elif branch.merge:
1632 syncbuf.info(self,
1633 'manifest no longer tracks %s',
1634 branch.merge)
1635
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001636 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001637 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001638 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001640 syncbuf.info(self,
1641 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001642 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001644 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001645 if not ID_RE.match(self.revisionExpr):
1646 # in case of manifest sync the revisionExpr might be a SHA1
1647 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001648 if not branch.merge.startswith('refs/'):
1649 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650 branch.Save()
1651
Mike Pontillod3153822012-02-28 11:53:24 -08001652 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001653 def _docopyandlink():
1654 self._CopyAndLinkFiles()
1655
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001656 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001657 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001658 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001659 if submodules:
1660 syncbuf.later2(self, _dosubmodules)
1661 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001662 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001664 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001665 if submodules:
1666 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001667 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001668 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001669 syncbuf.fail(self, e)
1670 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001672 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001673 if submodules:
1674 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001675
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001676 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001677 # dest should already be an absolute path, but src is project relative
1678 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001679 abssrc = os.path.join(self.worktree, src)
1680 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001682 def AddLinkFile(self, src, dest, absdest):
1683 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001684 # make src relative path to dest
1685 absdestdir = os.path.dirname(absdest)
1686 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001687 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001688
James W. Mills24c13082012-04-12 15:04:13 -05001689 def AddAnnotation(self, name, value, keep):
1690 self.annotations.append(_Annotation(name, value, keep))
1691
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001692 def DownloadPatchSet(self, change_id, patch_id):
1693 """Download a single patch set of a single change to FETCH_HEAD.
1694 """
1695 remote = self.GetRemote(self.remote.name)
1696
1697 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001698 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001699 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001700 if GitCommand(self, cmd, bare=True).Wait() != 0:
1701 return None
1702 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001703 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001704 change_id,
1705 patch_id,
1706 self.bare_git.rev_parse('FETCH_HEAD'))
1707
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001709# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001711 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712 """Create a new branch off the manifest's revision.
1713 """
Simran Basib9a1b732015-08-20 12:19:28 -07001714 if not branch_merge:
1715 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001716 head = self.work_git.GetHead()
1717 if head == (R_HEADS + name):
1718 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001719
David Pursehouse8a68ff92012-09-24 12:15:13 +09001720 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001721 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001722 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001723 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001724 capture_stdout=True,
1725 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001726
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001727 branch = self.GetBranch(name)
1728 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001729 branch.merge = branch_merge
1730 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1731 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001732
1733 if revision is None:
1734 revid = self.GetRevisionId(all_refs)
1735 else:
1736 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001737
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001738 if head.startswith(R_HEADS):
1739 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001740 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001741 except KeyError:
1742 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001743 if revid and head and revid == head:
1744 ref = os.path.join(self.gitdir, R_HEADS + name)
1745 try:
1746 os.makedirs(os.path.dirname(ref))
1747 except OSError:
1748 pass
1749 _lwrite(ref, '%s\n' % revid)
1750 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1751 'ref: %s%s\n' % (R_HEADS, name))
1752 branch.Save()
1753 return True
1754
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001755 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001756 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001757 capture_stdout=True,
1758 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001759 branch.Save()
1760 return True
1761 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762
Wink Saville02d79452009-04-10 13:01:24 -07001763 def CheckoutBranch(self, name):
1764 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001765
1766 Args:
1767 name: The name of the branch to checkout.
1768
1769 Returns:
1770 True if the checkout succeeded; False if it didn't; None if the branch
1771 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001772 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001773 rev = R_HEADS + name
1774 head = self.work_git.GetHead()
1775 if head == rev:
1776 # Already on the branch
1777 #
1778 return True
Wink Saville02d79452009-04-10 13:01:24 -07001779
David Pursehouse8a68ff92012-09-24 12:15:13 +09001780 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001781 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001782 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001783 except KeyError:
1784 # Branch does not exist in this project
1785 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001786 return None
Wink Saville02d79452009-04-10 13:01:24 -07001787
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001788 if head.startswith(R_HEADS):
1789 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001790 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001791 except KeyError:
1792 head = None
1793
1794 if head == revid:
1795 # Same revision; just update HEAD to point to the new
1796 # target branch, but otherwise take no other action.
1797 #
1798 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1799 'ref: %s%s\n' % (R_HEADS, name))
1800 return True
1801
1802 return GitCommand(self,
1803 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001804 capture_stdout=True,
1805 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001806
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001807 def AbandonBranch(self, name):
1808 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001809
1810 Args:
1811 name: The name of the branch to abandon.
1812
1813 Returns:
1814 True if the abandon succeeded; False if it didn't; None if the branch
1815 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001816 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001817 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001818 all_refs = self.bare_ref.all
1819 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001820 # Doesn't exist
1821 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001822
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001823 head = self.work_git.GetHead()
1824 if head == rev:
1825 # We can't destroy the branch while we are sitting
1826 # on it. Switch to a detached HEAD.
1827 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001828 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001829
David Pursehouse8a68ff92012-09-24 12:15:13 +09001830 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001831 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001832 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1833 '%s\n' % revid)
1834 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001835 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001836
1837 return GitCommand(self,
1838 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001839 capture_stdout=True,
1840 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001841
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842 def PruneHeads(self):
1843 """Prune any topic branches already merged into upstream.
1844 """
1845 cb = self.CurrentBranch
1846 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001847 left = self._allrefs
1848 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001849 if name.startswith(R_HEADS):
1850 name = name[len(R_HEADS):]
1851 if cb is None or name != cb:
1852 kill.append(name)
1853
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001854 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855 if cb is not None \
1856 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001857 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001858 self.work_git.DetachHead(HEAD)
1859 kill.append(cb)
1860
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001862 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001864 try:
1865 self.bare_git.DetachHead(rev)
1866
1867 b = ['branch', '-d']
1868 b.extend(kill)
1869 b = GitCommand(self, b, bare=True,
1870 capture_stdout=True,
1871 capture_stderr=True)
1872 b.Wait()
1873 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001874 if ID_RE.match(old):
1875 self.bare_git.DetachHead(old)
1876 else:
1877 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001878 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001879
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001880 for branch in kill:
1881 if (R_HEADS + branch) not in left:
1882 self.CleanPublishedCache()
1883 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001884
1885 if cb and cb not in kill:
1886 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001887 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001888
1889 kept = []
1890 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001891 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001892 branch = self.GetBranch(branch)
1893 base = branch.LocalMerge
1894 if not base:
1895 base = rev
1896 kept.append(ReviewableBranch(self, branch, base))
1897 return kept
1898
1899
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001900# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001901
1902 def GetRegisteredSubprojects(self):
1903 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001904
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001905 def rec(subprojects):
1906 if not subprojects:
1907 return
1908 result.extend(subprojects)
1909 for p in subprojects:
1910 rec(p.subprojects)
1911 rec(self.subprojects)
1912 return result
1913
1914 def _GetSubmodules(self):
1915 # Unfortunately we cannot call `git submodule status --recursive` here
1916 # because the working tree might not exist yet, and it cannot be used
1917 # without a working tree in its current implementation.
1918
1919 def get_submodules(gitdir, rev):
1920 # Parse .gitmodules for submodule sub_paths and sub_urls
1921 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1922 if not sub_paths:
1923 return []
1924 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1925 # revision of submodule repository
1926 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1927 submodules = []
1928 for sub_path, sub_url in zip(sub_paths, sub_urls):
1929 try:
1930 sub_rev = sub_revs[sub_path]
1931 except KeyError:
1932 # Ignore non-exist submodules
1933 continue
1934 submodules.append((sub_rev, sub_path, sub_url))
1935 return submodules
1936
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001937 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1938 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001939
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001940 def parse_gitmodules(gitdir, rev):
1941 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1942 try:
Anthony King7bdac712014-07-16 12:56:40 +01001943 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1944 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001945 except GitError:
1946 return [], []
1947 if p.Wait() != 0:
1948 return [], []
1949
1950 gitmodules_lines = []
1951 fd, temp_gitmodules_path = tempfile.mkstemp()
1952 try:
1953 os.write(fd, p.stdout)
1954 os.close(fd)
1955 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001956 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1957 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001958 if p.Wait() != 0:
1959 return [], []
1960 gitmodules_lines = p.stdout.split('\n')
1961 except GitError:
1962 return [], []
1963 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001964 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001965
1966 names = set()
1967 paths = {}
1968 urls = {}
1969 for line in gitmodules_lines:
1970 if not line:
1971 continue
1972 m = re_path.match(line)
1973 if m:
1974 names.add(m.group(1))
1975 paths[m.group(1)] = m.group(2)
1976 continue
1977 m = re_url.match(line)
1978 if m:
1979 names.add(m.group(1))
1980 urls[m.group(1)] = m.group(2)
1981 continue
1982 names = sorted(names)
1983 return ([paths.get(name, '') for name in names],
1984 [urls.get(name, '') for name in names])
1985
1986 def git_ls_tree(gitdir, rev, paths):
1987 cmd = ['ls-tree', rev, '--']
1988 cmd.extend(paths)
1989 try:
Anthony King7bdac712014-07-16 12:56:40 +01001990 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1991 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001992 except GitError:
1993 return []
1994 if p.Wait() != 0:
1995 return []
1996 objects = {}
1997 for line in p.stdout.split('\n'):
1998 if not line.strip():
1999 continue
2000 object_rev, object_path = line.split()[2:4]
2001 objects[object_path] = object_rev
2002 return objects
2003
2004 try:
2005 rev = self.GetRevisionId()
2006 except GitError:
2007 return []
2008 return get_submodules(self.gitdir, rev)
2009
2010 def GetDerivedSubprojects(self):
2011 result = []
2012 if not self.Exists:
2013 # If git repo does not exist yet, querying its submodules will
2014 # mess up its states; so return here.
2015 return result
2016 for rev, path, url in self._GetSubmodules():
2017 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002018 relpath, worktree, gitdir, objdir = \
2019 self.manifest.GetSubprojectPaths(self, name, path)
2020 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002021 if project:
2022 result.extend(project.GetDerivedSubprojects())
2023 continue
David James8d201162013-10-11 17:03:19 -07002024
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002025 if url.startswith('..'):
2026 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002027 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002028 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002029 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002030 review=self.remote.review,
2031 revision=self.remote.revision)
2032 subproject = Project(manifest=self.manifest,
2033 name=name,
2034 remote=remote,
2035 gitdir=gitdir,
2036 objdir=objdir,
2037 worktree=worktree,
2038 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002039 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002040 revisionId=rev,
2041 rebase=self.rebase,
2042 groups=self.groups,
2043 sync_c=self.sync_c,
2044 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002045 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002046 parent=self,
2047 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002048 result.append(subproject)
2049 result.extend(subproject.GetDerivedSubprojects())
2050 return result
2051
2052
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002053# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002054 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002055 try:
2056 # if revision (sha or tag) is not present then following function
2057 # throws an error.
2058 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2059 return True
2060 except GitError:
2061 # There is no such persistent revision. We have to fetch it.
2062 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002063
Julien Campergue335f5ef2013-10-16 11:02:35 +02002064 def _FetchArchive(self, tarpath, cwd=None):
2065 cmd = ['archive', '-v', '-o', tarpath]
2066 cmd.append('--remote=%s' % self.remote.url)
2067 cmd.append('--prefix=%s/' % self.relpath)
2068 cmd.append(self.revisionExpr)
2069
2070 command = GitCommand(self, cmd, cwd=cwd,
2071 capture_stdout=True,
2072 capture_stderr=True)
2073
2074 if command.Wait() != 0:
2075 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2076
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002077 def _RemoteFetch(self, name=None,
2078 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002079 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002080 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002081 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002082 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002083 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002084 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002085 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002086 force_sync=False,
2087 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002088
2089 is_sha1 = False
2090 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002091 # The depth should not be used when fetching to a mirror because
2092 # it will result in a shallow repository that cannot be cloned or
2093 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002094 # The repo project should also never be synced with partial depth.
2095 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2096 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002097
Shawn Pearce69e04d82014-01-29 12:48:54 -08002098 if depth:
2099 current_branch_only = True
2100
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002101 if ID_RE.match(self.revisionExpr) is not None:
2102 is_sha1 = True
2103
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002104 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002105 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002106 # this is a tag and its sha1 value should never change
2107 tag_name = self.revisionExpr[len(R_TAGS):]
2108
2109 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002110 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002111 if not quiet:
2112 print('Skipped fetching project %s (already have persistent ref)'
2113 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002114 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002115 if is_sha1 and not depth:
2116 # When syncing a specific commit and --depth is not set:
2117 # * if upstream is explicitly specified and is not a sha1, fetch only
2118 # upstream as users expect only upstream to be fetch.
2119 # Note: The commit might not be in upstream in which case the sync
2120 # will fail.
2121 # * otherwise, fetch all branches to make sure we end up with the
2122 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002123 if self.upstream:
2124 current_branch_only = not ID_RE.match(self.upstream)
2125 else:
2126 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002127
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128 if not name:
2129 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002130
2131 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002132 remote = self.GetRemote(name)
2133 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002134 ssh_proxy = True
2135
Shawn O. Pearce88443382010-10-08 10:02:09 +02002136 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002137 if alt_dir and 'objects' == os.path.basename(alt_dir):
2138 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002139 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2140 remote = self.GetRemote(name)
2141
David Pursehouse8a68ff92012-09-24 12:15:13 +09002142 all_refs = self.bare_ref.all
2143 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002144 tmp = set()
2145
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302146 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002147 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002148 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002149 all_refs[r] = ref_id
2150 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151 continue
2152
David Pursehouse8a68ff92012-09-24 12:15:13 +09002153 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002154 continue
2155
David Pursehouse8a68ff92012-09-24 12:15:13 +09002156 r = 'refs/_alt/%s' % ref_id
2157 all_refs[r] = ref_id
2158 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002159 tmp.add(r)
2160
heping3d7bbc92017-04-12 19:51:47 +08002161 tmp_packed_lines = []
2162 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002163
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302164 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002165 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002166 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002167 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002168 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169
heping3d7bbc92017-04-12 19:51:47 +08002170 tmp_packed = ''.join(tmp_packed_lines)
2171 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002172 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002174 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002175
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002176 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002177
Xin Li745be2e2019-06-03 11:24:30 -07002178 if clone_filter:
2179 git_require((2, 19, 0), fail=True, msg='partial clones')
2180 cmd.append('--filter=%s' % clone_filter)
2181 self.config.SetString('extensions.partialclone', self.remote.name)
2182
Conley Owensf97e8382015-01-21 11:12:46 -08002183 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002184 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002185 else:
2186 # If this repo has shallow objects, then we don't know which refs have
2187 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2188 # do this with projects that don't have shallow objects, since it is less
2189 # efficient.
2190 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2191 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002192
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002193 if quiet:
2194 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002195 if not self.worktree:
2196 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002197 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002198
Xin Lia2cd6ae2019-09-16 10:55:41 -07002199 spec = []
2200
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002201 # If using depth then we should not get all the tags since they may
2202 # be outside of the depth.
2203 if no_tags or depth:
2204 cmd.append('--no-tags')
2205 else:
2206 cmd.append('--tags')
Xin Lia2cd6ae2019-09-16 10:55:41 -07002207 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002208
Mike Frysingere57f1142019-03-18 21:27:54 -04002209 if force_sync:
2210 cmd.append('--force')
2211
David Pursehouse74cfd272015-10-14 10:50:15 +09002212 if prune:
2213 cmd.append('--prune')
2214
Martin Kellye4e94d22017-03-21 16:05:12 -07002215 if submodules:
2216 cmd.append('--recurse-submodules=on-demand')
2217
Brian Harring14a66742012-09-28 20:21:57 -07002218 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002219 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002220 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002221 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002222 spec.append('tag')
2223 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002224
David Pursehouse403b64e2015-04-27 10:41:33 +09002225 if not self.manifest.IsMirror:
2226 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002227 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002228 # Shallow checkout of a specific commit, fetch from that commit and not
2229 # the heads only as the commit might be deeper in the history.
2230 spec.append(branch)
2231 else:
2232 if is_sha1:
2233 branch = self.upstream
2234 if branch is not None and branch.strip():
2235 if not branch.startswith('refs/'):
2236 branch = R_HEADS + branch
2237 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002238 cmd.extend(spec)
2239
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002240 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002241 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002242 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002243 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002244 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002245 ok = True
2246 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002247 # If needed, run the 'git remote prune' the first time through the loop
2248 elif (not _i and
2249 "error:" in gitcmd.stderr and
2250 "git remote prune" in gitcmd.stderr):
2251 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002252 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002253 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002254 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002255 break
2256 continue
Brian Harring14a66742012-09-28 20:21:57 -07002257 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002258 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2259 # in sha1 mode, we just tried sync'ing from the upstream field; it
2260 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002261 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002262 elif ret < 0:
2263 # Git died with a signal, exit immediately
2264 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002265 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002266
2267 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002268 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002269 if old_packed != '':
2270 _lwrite(packed_refs, old_packed)
2271 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002272 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002273 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002274
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002275 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002276 # We just synced the upstream given branch; verify we
2277 # got what we wanted, else trigger a second run of all
2278 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002279 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002280 if current_branch_only and depth:
2281 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002282 return self._RemoteFetch(name=name,
2283 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002284 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002285 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002286 else:
2287 # Avoid infinite recursion: sync all branches with depth set to None
2288 return self._RemoteFetch(name=name, current_branch_only=False,
2289 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002290 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002291
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002292 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002294 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002295 if initial and \
2296 (self.manifest.manifestProject.config.GetString('repo.depth') or
2297 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002298 return False
2299
2300 remote = self.GetRemote(self.remote.name)
2301 bundle_url = remote.url + '/clone.bundle'
2302 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002303 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2304 'persistent-http',
2305 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002306 return False
2307
2308 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2309 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2310
2311 exist_dst = os.path.exists(bundle_dst)
2312 exist_tmp = os.path.exists(bundle_tmp)
2313
2314 if not initial and not exist_dst and not exist_tmp:
2315 return False
2316
2317 if not exist_dst:
2318 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2319 if not exist_dst:
2320 return False
2321
2322 cmd = ['fetch']
2323 if quiet:
2324 cmd.append('--quiet')
2325 if not self.worktree:
2326 cmd.append('--update-head-ok')
2327 cmd.append(bundle_dst)
2328 for f in remote.fetch:
2329 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002330 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002331
2332 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002333 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002334 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002335 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002336 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002337 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002338
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002339 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002340 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002341 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002342
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002343 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002344 if quiet:
2345 cmd += ['--silent']
2346 if os.path.exists(tmpPath):
2347 size = os.stat(tmpPath).st_size
2348 if size >= 1024:
2349 cmd += ['--continue-at', '%d' % (size,)]
2350 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002351 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002352 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002353 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002354 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002355 if proxy:
2356 cmd += ['--proxy', proxy]
2357 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2358 cmd += ['--proxy', os.environ['http_proxy']]
2359 if srcUrl.startswith('persistent-https'):
2360 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2361 elif srcUrl.startswith('persistent-http'):
2362 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002363 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002364
Dave Borowitz137d0132015-01-02 11:12:54 -08002365 if IsTrace():
2366 Trace('%s', ' '.join(cmd))
2367 try:
2368 proc = subprocess.Popen(cmd)
2369 except OSError:
2370 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002371
Dave Borowitz137d0132015-01-02 11:12:54 -08002372 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002373
Dave Borowitz137d0132015-01-02 11:12:54 -08002374 if curlret == 22:
2375 # From curl man page:
2376 # 22: HTTP page not retrieved. The requested url was not found or
2377 # returned another error with the HTTP error code being 400 or above.
2378 # This return code only appears if -f, --fail is used.
2379 if not quiet:
2380 print("Server does not provide clone.bundle; ignoring.",
2381 file=sys.stderr)
2382 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002383
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002384 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002385 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002386 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002387 return True
2388 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002389 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002390 return False
2391 else:
2392 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002393
Kris Giesingc8d882a2014-12-23 13:02:32 -08002394 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002395 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002396 with open(path, 'rb') as f:
2397 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002398 return True
2399 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002400 if not quiet:
2401 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002402 return False
2403 except OSError:
2404 return False
2405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002406 def _Checkout(self, rev, quiet=False):
2407 cmd = ['checkout']
2408 if quiet:
2409 cmd.append('-q')
2410 cmd.append(rev)
2411 cmd.append('--')
2412 if GitCommand(self, cmd).Wait() != 0:
2413 if self._allrefs:
2414 raise GitError('%s checkout %s ' % (self.name, rev))
2415
Anthony King7bdac712014-07-16 12:56:40 +01002416 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002417 cmd = ['cherry-pick']
2418 cmd.append(rev)
2419 cmd.append('--')
2420 if GitCommand(self, cmd).Wait() != 0:
2421 if self._allrefs:
2422 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2423
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302424 def _LsRemote(self, refs):
2425 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302426 p = GitCommand(self, cmd, capture_stdout=True)
2427 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002428 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302429 return None
2430
Anthony King7bdac712014-07-16 12:56:40 +01002431 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002432 cmd = ['revert']
2433 cmd.append('--no-edit')
2434 cmd.append(rev)
2435 cmd.append('--')
2436 if GitCommand(self, cmd).Wait() != 0:
2437 if self._allrefs:
2438 raise GitError('%s revert %s ' % (self.name, rev))
2439
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002440 def _ResetHard(self, rev, quiet=True):
2441 cmd = ['reset', '--hard']
2442 if quiet:
2443 cmd.append('-q')
2444 cmd.append(rev)
2445 if GitCommand(self, cmd).Wait() != 0:
2446 raise GitError('%s reset --hard %s ' % (self.name, rev))
2447
Martin Kellye4e94d22017-03-21 16:05:12 -07002448 def _SyncSubmodules(self, quiet=True):
2449 cmd = ['submodule', 'update', '--init', '--recursive']
2450 if quiet:
2451 cmd.append('-q')
2452 if GitCommand(self, cmd).Wait() != 0:
2453 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2454
Anthony King7bdac712014-07-16 12:56:40 +01002455 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002456 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 if onto is not None:
2458 cmd.extend(['--onto', onto])
2459 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002460 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 raise GitError('%s rebase %s ' % (self.name, upstream))
2462
Pierre Tardy3d125942012-05-04 12:18:12 +02002463 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002464 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002465 if ffonly:
2466 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 if GitCommand(self, cmd).Wait() != 0:
2468 raise GitError('%s merge %s ' % (self.name, head))
2469
Kevin Degiabaa7f32014-11-12 11:27:45 -07002470 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002471 init_git_dir = not os.path.exists(self.gitdir)
2472 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002473 try:
2474 # Initialize the bare repository, which contains all of the objects.
2475 if init_obj_dir:
2476 os.makedirs(self.objdir)
2477 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002478
Kevin Degib1a07b82015-07-27 13:33:43 -06002479 # If we have a separate directory to hold refs, initialize it as well.
2480 if self.objdir != self.gitdir:
2481 if init_git_dir:
2482 os.makedirs(self.gitdir)
2483
2484 if init_obj_dir or init_git_dir:
2485 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2486 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002487 try:
2488 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2489 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002490 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002491 print("Retrying clone after deleting %s" %
2492 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002493 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002494 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2495 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002496 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002497 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002498 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2499 except:
2500 raise e
2501 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002502
Kevin Degi384b3c52014-10-16 16:02:58 -06002503 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002504 mp = self.manifest.manifestProject
2505 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002506
Kevin Degib1a07b82015-07-27 13:33:43 -06002507 if ref_dir or mirror_git:
2508 if not mirror_git:
2509 mirror_git = os.path.join(ref_dir, self.name + '.git')
2510 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2511 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002512
Kevin Degib1a07b82015-07-27 13:33:43 -06002513 if os.path.exists(mirror_git):
2514 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002515
Kevin Degib1a07b82015-07-27 13:33:43 -06002516 elif os.path.exists(repo_git):
2517 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002518
Kevin Degib1a07b82015-07-27 13:33:43 -06002519 else:
2520 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002521
Kevin Degib1a07b82015-07-27 13:33:43 -06002522 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002523 if not os.path.isabs(ref_dir):
2524 # The alternate directory is relative to the object database.
2525 ref_dir = os.path.relpath(ref_dir,
2526 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002527 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2528 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002529
Kevin Degib1a07b82015-07-27 13:33:43 -06002530 self._UpdateHooks()
2531
2532 m = self.manifest.manifestProject.config
2533 for key in ['user.name', 'user.email']:
2534 if m.Has(key, include_defaults=False):
2535 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002536 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002537 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002538 if self.manifest.IsMirror:
2539 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002540 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002541 self.config.SetString('core.bare', None)
2542 except Exception:
2543 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002544 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002545 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002546 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002547 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002548
Jimmie Westera0444582012-10-24 13:44:42 +02002549 def _UpdateHooks(self):
2550 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002551 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002553 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002554 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002555 if not os.path.exists(hooks):
2556 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002557 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002558 name = os.path.basename(stock_hook)
2559
Victor Boivie65e0f352011-04-18 11:23:29 +02002560 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002561 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002562 # Don't install a Gerrit Code Review hook if this
2563 # project does not appear to use it for reviews.
2564 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002565 # Since the manifest project is one of those, but also
2566 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002567 continue
2568
2569 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002570 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002571 continue
2572 if os.path.exists(dst):
2573 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002574 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002575 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002576 _warn("%s: Not replacing locally modified %s hook",
2577 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002578 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002579 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002580 platform_utils.symlink(
2581 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002582 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002583 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002584 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002585 else:
2586 raise
2587
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002589 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002590 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002591 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002592 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002593 remote.review = self.remote.review
2594 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002596 if self.worktree:
2597 remote.ResetFetch(mirror=False)
2598 else:
2599 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002600 remote.Save()
2601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602 def _InitMRef(self):
2603 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002604 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002606 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002607 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002608
2609 def _InitAnyMRef(self, ref):
2610 cur = self.bare_ref.symref(ref)
2611
2612 if self.revisionId:
2613 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2614 msg = 'manifest set to %s' % self.revisionId
2615 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002616 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002617 else:
2618 remote = self.GetRemote(self.remote.name)
2619 dst = remote.ToLocal(self.revisionExpr)
2620 if cur != dst:
2621 msg = 'manifest set to %s' % self.revisionExpr
2622 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002623
Kevin Degi384b3c52014-10-16 16:02:58 -06002624 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002625 symlink_files = self.shareable_files[:]
2626 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002627 if share_refs:
2628 symlink_files += self.working_tree_files
2629 symlink_dirs += self.working_tree_dirs
2630 to_symlink = symlink_files + symlink_dirs
2631 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002632 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002633 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002634 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002635 # Fail if the links are pointing to the wrong place
2636 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002637 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002638 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002639 'work tree. If you\'re comfortable with the '
2640 'possibility of losing the work tree\'s git metadata,'
2641 ' use `repo sync --force-sync {0}` to '
2642 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002643
David James8d201162013-10-11 17:03:19 -07002644 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2645 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2646
2647 Args:
2648 gitdir: The bare git repository. Must already be initialized.
2649 dotgit: The repository you would like to initialize.
2650 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2651 Only one work tree can store refs under a given |gitdir|.
2652 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2653 This saves you the effort of initializing |dotgit| yourself.
2654 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002655 symlink_files = self.shareable_files[:]
2656 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002657 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002658 symlink_files += self.working_tree_files
2659 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002660 to_symlink = symlink_files + symlink_dirs
2661
2662 to_copy = []
2663 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002664 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002665
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002666 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002667 for name in set(to_copy).union(to_symlink):
2668 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002669 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002670 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002671
Kevin Degi384b3c52014-10-16 16:02:58 -06002672 if os.path.lexists(dst):
2673 continue
David James8d201162013-10-11 17:03:19 -07002674
2675 # If the source dir doesn't exist, create an empty dir.
2676 if name in symlink_dirs and not os.path.lexists(src):
2677 os.makedirs(src)
2678
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002679 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002680 platform_utils.symlink(
2681 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002682 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002683 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002684 shutil.copytree(src, dst)
2685 elif os.path.isfile(src):
2686 shutil.copy(src, dst)
2687
Conley Owens80b87fe2014-05-09 17:13:44 -07002688 # If the source file doesn't exist, ensure the destination
2689 # file doesn't either.
2690 if name in symlink_files and not os.path.lexists(src):
2691 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002692 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002693 except OSError:
2694 pass
2695
David James8d201162013-10-11 17:03:19 -07002696 except OSError as e:
2697 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002698 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002699 else:
2700 raise
2701
Martin Kellye4e94d22017-03-21 16:05:12 -07002702 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002703 realdotgit = os.path.join(self.worktree, '.git')
2704 tmpdotgit = realdotgit + '.tmp'
2705 init_dotgit = not os.path.exists(realdotgit)
2706 if init_dotgit:
2707 dotgit = tmpdotgit
2708 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2709 os.makedirs(tmpdotgit)
2710 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2711 copy_all=False)
2712 else:
2713 dotgit = realdotgit
2714
Kevin Degib1a07b82015-07-27 13:33:43 -06002715 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002716 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2717 except GitError as e:
2718 if force_sync and not init_dotgit:
2719 try:
2720 platform_utils.rmtree(dotgit)
2721 return self._InitWorkTree(force_sync=False, submodules=submodules)
2722 except:
2723 raise e
2724 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725
Mike Frysingerf4545122019-11-11 04:34:16 -05002726 if init_dotgit:
2727 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002728
Mike Frysingerf4545122019-11-11 04:34:16 -05002729 # Now that the .git dir is fully set up, move it to its final home.
2730 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731
Mike Frysingerf4545122019-11-11 04:34:16 -05002732 # Finish checking out the worktree.
2733 cmd = ['read-tree', '--reset', '-u']
2734 cmd.append('-v')
2735 cmd.append(HEAD)
2736 if GitCommand(self, cmd).Wait() != 0:
2737 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002738
Mike Frysingerf4545122019-11-11 04:34:16 -05002739 if submodules:
2740 self._SyncSubmodules(quiet=True)
2741 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002742
Renaud Paquay788e9622017-01-27 11:41:12 -08002743 def _get_symlink_error_message(self):
2744 if platform_utils.isWindows():
2745 return ('Unable to create symbolic link. Please re-run the command as '
2746 'Administrator, or see '
2747 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2748 'for other options.')
2749 return 'filesystem must support symlinks'
2750
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002751 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002752 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002754 def _revlist(self, *args, **kw):
2755 a = []
2756 a.extend(args)
2757 a.append('--')
2758 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002759
2760 @property
2761 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002762 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002763
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002764 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002765 """Get logs between two revisions of this project."""
2766 comp = '..'
2767 if rev1:
2768 revs = [rev1]
2769 if rev2:
2770 revs.extend([comp, rev2])
2771 cmd = ['log', ''.join(revs)]
2772 out = DiffColoring(self.config)
2773 if out.is_on and color:
2774 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002775 if pretty_format is not None:
2776 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002777 if oneline:
2778 cmd.append('--oneline')
2779
2780 try:
2781 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2782 if log.Wait() == 0:
2783 return log.stdout
2784 except GitError:
2785 # worktree may not exist if groups changed for example. In that case,
2786 # try in gitdir instead.
2787 if not os.path.exists(self.worktree):
2788 return self.bare_git.log(*cmd[1:])
2789 else:
2790 raise
2791 return None
2792
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002793 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2794 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002795 """Get the list of logs from this revision to given revisionId"""
2796 logs = {}
2797 selfId = self.GetRevisionId(self._allrefs)
2798 toId = toProject.GetRevisionId(toProject._allrefs)
2799
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002800 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2801 pretty_format=pretty_format)
2802 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2803 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002804 return logs
2805
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002806 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002807
David James8d201162013-10-11 17:03:19 -07002808 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809 self._project = project
2810 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002811 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002813 def LsOthers(self):
2814 p = GitCommand(self._project,
2815 ['ls-files',
2816 '-z',
2817 '--others',
2818 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002819 bare=False,
David James8d201162013-10-11 17:03:19 -07002820 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002821 capture_stdout=True,
2822 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002823 if p.Wait() == 0:
2824 out = p.stdout
2825 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002826 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002827 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002828 return []
2829
2830 def DiffZ(self, name, *args):
2831 cmd = [name]
2832 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002833 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002834 cmd.extend(args)
2835 p = GitCommand(self._project,
2836 cmd,
David James8d201162013-10-11 17:03:19 -07002837 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002838 bare=False,
2839 capture_stdout=True,
2840 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002841 try:
2842 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002843 if not hasattr(out, 'encode'):
2844 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002845 r = {}
2846 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002847 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002848 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002849 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002850 info = next(out)
2851 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002852 except StopIteration:
2853 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002854
2855 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002856
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002857 def __init__(self, path, omode, nmode, oid, nid, state):
2858 self.path = path
2859 self.src_path = None
2860 self.old_mode = omode
2861 self.new_mode = nmode
2862 self.old_id = oid
2863 self.new_id = nid
2864
2865 if len(state) == 1:
2866 self.status = state
2867 self.level = None
2868 else:
2869 self.status = state[:1]
2870 self.level = state[1:]
2871 while self.level.startswith('0'):
2872 self.level = self.level[1:]
2873
2874 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002875 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002876 if info.status in ('R', 'C'):
2877 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002878 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002879 r[info.path] = info
2880 return r
2881 finally:
2882 p.Wait()
2883
2884 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002885 if self._bare:
2886 path = os.path.join(self._project.gitdir, HEAD)
2887 else:
2888 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002889 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002890 with open(path) as fd:
2891 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002892 except IOError as e:
2893 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002894 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302895 line = line.decode()
2896 except AttributeError:
2897 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002898 if line.startswith('ref: '):
2899 return line[5:-1]
2900 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002901
2902 def SetHead(self, ref, message=None):
2903 cmdv = []
2904 if message is not None:
2905 cmdv.extend(['-m', message])
2906 cmdv.append(HEAD)
2907 cmdv.append(ref)
2908 self.symbolic_ref(*cmdv)
2909
2910 def DetachHead(self, new, message=None):
2911 cmdv = ['--no-deref']
2912 if message is not None:
2913 cmdv.extend(['-m', message])
2914 cmdv.append(HEAD)
2915 cmdv.append(new)
2916 self.update_ref(*cmdv)
2917
2918 def UpdateRef(self, name, new, old=None,
2919 message=None,
2920 detach=False):
2921 cmdv = []
2922 if message is not None:
2923 cmdv.extend(['-m', message])
2924 if detach:
2925 cmdv.append('--no-deref')
2926 cmdv.append(name)
2927 cmdv.append(new)
2928 if old is not None:
2929 cmdv.append(old)
2930 self.update_ref(*cmdv)
2931
2932 def DeleteRef(self, name, old=None):
2933 if not old:
2934 old = self.rev_parse(name)
2935 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002936 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002938 def rev_list(self, *args, **kw):
2939 if 'format' in kw:
2940 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2941 else:
2942 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002943 cmdv.extend(args)
2944 p = GitCommand(self._project,
2945 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002946 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002947 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002948 capture_stdout=True,
2949 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002950 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002951 raise GitError('%s rev-list %s: %s' %
2952 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002953 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954
2955 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002956 """Allow arbitrary git commands using pythonic syntax.
2957
2958 This allows you to do things like:
2959 git_obj.rev_parse('HEAD')
2960
2961 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2962 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002963 Any other positional arguments will be passed to the git command, and the
2964 following keyword arguments are supported:
2965 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002966
2967 Args:
2968 name: The name of the git command to call. Any '_' characters will
2969 be replaced with '-'.
2970
2971 Returns:
2972 A callable object that will try to call git with the named command.
2973 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002975
Dave Borowitz091f8932012-10-23 17:01:04 -07002976 def runner(*args, **kwargs):
2977 cmdv = []
2978 config = kwargs.pop('config', None)
2979 for k in kwargs:
2980 raise TypeError('%s() got an unexpected keyword argument %r'
2981 % (name, k))
2982 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002983 if not git_require((1, 7, 2)):
2984 raise ValueError('cannot set config on command line for %s()'
2985 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302986 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002987 cmdv.append('-c')
2988 cmdv.append('%s=%s' % (k, v))
2989 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002990 cmdv.extend(args)
2991 p = GitCommand(self._project,
2992 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002993 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002994 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002995 capture_stdout=True,
2996 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002997 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002998 raise GitError('%s %s: %s' %
2999 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003000 r = p.stdout
3001 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3002 return r[:-1]
3003 return r
3004 return runner
3005
3006
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003007class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003008
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003009 def __str__(self):
3010 return 'prior sync failed; rebase still in progress'
3011
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003012
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003013class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003014
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003015 def __str__(self):
3016 return 'contains uncommitted changes'
3017
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003018
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003019class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003020
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003021 def __init__(self, project, text):
3022 self.project = project
3023 self.text = text
3024
3025 def Print(self, syncbuf):
3026 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3027 syncbuf.out.nl()
3028
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003029
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003030class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003031
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003032 def __init__(self, project, why):
3033 self.project = project
3034 self.why = why
3035
3036 def Print(self, syncbuf):
3037 syncbuf.out.fail('error: %s/: %s',
3038 self.project.relpath,
3039 str(self.why))
3040 syncbuf.out.nl()
3041
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003042
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003043class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003044
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003045 def __init__(self, project, action):
3046 self.project = project
3047 self.action = action
3048
3049 def Run(self, syncbuf):
3050 out = syncbuf.out
3051 out.project('project %s/', self.project.relpath)
3052 out.nl()
3053 try:
3054 self.action()
3055 out.nl()
3056 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003057 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003058 out.nl()
3059 return False
3060
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003061
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003062class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003063
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003064 def __init__(self, config):
3065 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003066 self.project = self.printer('header', attr='bold')
3067 self.info = self.printer('info')
3068 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003069
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003070
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003071class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003072
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003073 def __init__(self, config, detach_head=False):
3074 self._messages = []
3075 self._failures = []
3076 self._later_queue1 = []
3077 self._later_queue2 = []
3078
3079 self.out = _SyncColoring(config)
3080 self.out.redirect(sys.stderr)
3081
3082 self.detach_head = detach_head
3083 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003084 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003085
3086 def info(self, project, fmt, *args):
3087 self._messages.append(_InfoMessage(project, fmt % args))
3088
3089 def fail(self, project, err=None):
3090 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003091 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003092
3093 def later1(self, project, what):
3094 self._later_queue1.append(_Later(project, what))
3095
3096 def later2(self, project, what):
3097 self._later_queue2.append(_Later(project, what))
3098
3099 def Finish(self):
3100 self._PrintMessages()
3101 self._RunLater()
3102 self._PrintMessages()
3103 return self.clean
3104
David Rileye0684ad2017-04-05 00:02:59 -07003105 def Recently(self):
3106 recent_clean = self.recent_clean
3107 self.recent_clean = True
3108 return recent_clean
3109
3110 def _MarkUnclean(self):
3111 self.clean = False
3112 self.recent_clean = False
3113
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003114 def _RunLater(self):
3115 for q in ['_later_queue1', '_later_queue2']:
3116 if not self._RunQueue(q):
3117 return
3118
3119 def _RunQueue(self, queue):
3120 for m in getattr(self, queue):
3121 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003122 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003123 return False
3124 setattr(self, queue, [])
3125 return True
3126
3127 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003128 if self._messages or self._failures:
3129 if os.isatty(2):
3130 self.out.write(progress.CSI_ERASE_LINE)
3131 self.out.write('\r')
3132
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003133 for m in self._messages:
3134 m.Print(self)
3135 for m in self._failures:
3136 m.Print(self)
3137
3138 self._messages = []
3139 self._failures = []
3140
3141
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003143
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003144 """A special project housed under .repo.
3145 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003147 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003148 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003149 manifest=manifest,
3150 name=name,
3151 gitdir=gitdir,
3152 objdir=gitdir,
3153 worktree=worktree,
3154 remote=RemoteSpec('origin'),
3155 relpath='.repo/%s' % name,
3156 revisionExpr='refs/heads/master',
3157 revisionId=None,
3158 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003159
3160 def PreSync(self):
3161 if self.Exists:
3162 cb = self.CurrentBranch
3163 if cb:
3164 base = self.GetBranch(cb).merge
3165 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003166 self.revisionExpr = base
3167 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168
Martin Kelly224a31a2017-07-10 14:46:25 -07003169 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003170 """ Prepare MetaProject for manifest branch switch
3171 """
3172
3173 # detach and delete manifest branch, allowing a new
3174 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003175 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003176 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003177 syncbuf.Finish()
3178
3179 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003180 ['update-ref', '-d', 'refs/heads/default'],
3181 capture_stdout=True,
3182 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003184 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003185 def LastFetch(self):
3186 try:
3187 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3188 return os.path.getmtime(fh)
3189 except OSError:
3190 return 0
3191
3192 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003193 def HasChanges(self):
3194 """Has the remote received new commits not yet checked out?
3195 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003196 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003197 return False
3198
David Pursehouse8a68ff92012-09-24 12:15:13 +09003199 all_refs = self.bare_ref.all
3200 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003201 head = self.work_git.GetHead()
3202 if head.startswith(R_HEADS):
3203 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003204 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003205 except KeyError:
3206 head = None
3207
3208 if revid == head:
3209 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003210 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003211 return True
3212 return False