blob: 2f634d9e76efa3a51588615d6bb5cdc2e17fc25c [file] [log] [blame]
Jackeagle32d06d02019-09-18 02:43:26 -04001#!/usr/bin/env python
2# -*- coding:utf-8 -*-
3
4"""Repo launcher.
5
6This is a standalone tool that people may copy to anywhere in their system.
7It is used to get an initial repo client checkout, and after that it runs the
8copy of repo in the checkout.
9"""
10
11from __future__ import print_function
12
13# repo default configuration
14#
15import os
16REPO_URL = os.environ.get('REPO_URL', None)
17if not REPO_URL:
18 REPO_URL = 'https://gerrit.googlesource.com/git-repo'
rohanc384dfe2020-02-19 23:30:17 -050019REPO_REV = 'stable'
Jackeagle32d06d02019-09-18 02:43:26 -040020
21# Copyright (C) 2008 Google Inc.
22#
23# Licensed under the Apache License, Version 2.0 (the "License");
24# you may not use this file except in compliance with the License.
25# You may obtain a copy of the License at
26#
27# http://www.apache.org/licenses/LICENSE-2.0
28#
29# Unless required by applicable law or agreed to in writing, software
30# distributed under the License is distributed on an "AS IS" BASIS,
31# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32# See the License for the specific language governing permissions and
33# limitations under the License.
34
35# increment this whenever we make important changes to this script
rohanc384dfe2020-02-19 23:30:17 -050036VERSION = (1, 26)
Jackeagle32d06d02019-09-18 02:43:26 -040037
38# increment this if the MAINTAINER_KEYS block is modified
rohanc384dfe2020-02-19 23:30:17 -050039KEYRING_VERSION = (1, 2)
Jackeagle32d06d02019-09-18 02:43:26 -040040
41# Each individual key entry is created by using:
42# gpg --armor --export keyid
43MAINTAINER_KEYS = """
44
45 Repo Maintainer <repo@android.kernel.org>
46-----BEGIN PGP PUBLIC KEY BLOCK-----
47Version: GnuPG v1.4.2.2 (GNU/Linux)
48
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
84=CMiZ
85-----END PGP PUBLIC KEY BLOCK-----
rohanc384dfe2020-02-19 23:30:17 -050086
87 Conley Owens <cco3@android.com>
88-----BEGIN PGP PUBLIC KEY BLOCK-----
89Version: GnuPG v1.4.11 (GNU/Linux)
90
91mQENBFHRvc8BCADFg45Xx/y6QDC+T7Y/gGc7vx0ww7qfOwIKlAZ9xG3qKunMxo+S
92hPCnzEl3cq+6I1Ww/ndop/HB3N3toPXRCoN8Vs4/Hc7by+SnaLFnacrm+tV5/OgT
93V37Lzt8lhay1Kl+YfpFwHYYpIEBLFV9knyfRXS/428W2qhdzYfvB15/AasRmwmor
94py4NIzSs8UD/SPr1ihqNCdZM76+MQyN5HMYXW/ALZXUFG0pwluHFA7hrfPG74i8C
95zMiP7qvMWIl/r/jtzHioH1dRKgbod+LZsrDJ8mBaqsZaDmNJMhss9g76XvfMyLra
969DI9/iFuBpGzeqBv0hwOGQspLRrEoyTeR6n1ABEBAAG0H0NvbmxleSBPd2VucyA8
97Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlHRvc8CGwMGCwkIBwMCBhUIAgkK
98CwQWAgMBAh4BAheAAAoJEGe35EhpKzgsP6AIAJKJmNtn4l7hkYHKHFSo3egb6RjQ
99zEIP3MFTcu8HFX1kF1ZFbrp7xqurLaE53kEkKuAAvjJDAgI8mcZHP1JyplubqjQA
100xvv84gK+OGP3Xk+QK1ZjUQSbjOpjEiSZpRhWcHci3dgOUH4blJfByHw25hlgHowd
101a/2PrNKZVcJ92YienaxxGjcXEUcd0uYEG2+rwllQigFcnMFDhr9B71MfalRHjFKE
102fmdoypqLrri61YBc59P88Rw2/WUpTQjgNubSqa3A2+CKdaRyaRw+2fdF4TdR0h8W
103zbg+lbaPtJHsV+3mJC7fq26MiJDRJa5ZztpMn8su20gbLgi2ShBOaHAYDDi5AQ0E
104UdG9zwEIAMoOBq+QLNozAhxOOl5GL3StTStGRgPRXINfmViTsihrqGCWBBUfXlUE
105OytC0mYcrDUQev/8ToVoyqw+iGSwDkcSXkrEUCKFtHV/GECWtk1keyHgR10YKI1R
106mquSXoubWGqPeG1PAI74XWaRx8UrL8uCXUtmD8Q5J7mDjKR5NpxaXrwlA0bKsf2E
107Gp9tu1kKauuToZhWHMRMqYSOGikQJwWSFYKT1KdNcOXLQF6+bfoJ6sjVYdwfmNQL
108Ixn8QVhoTDedcqClSWB17VDEFDFa7MmqXZz2qtM3X1R/MUMHqPtegQzBGNhRdnI2
109V45+1Nnx/uuCxDbeI4RbHzujnxDiq70AEQEAAYkBHwQYAQIACQUCUdG9zwIbDAAK
110CRBnt+RIaSs4LNVeB/0Y2pZ8I7gAAcEM0Xw8drr4omg2fUoK1J33ozlA/RxeA/lJ
111I3KnyCDTpXuIeBKPGkdL8uMATC9Z8DnBBajRlftNDVZS3Hz4G09G9QpMojvJkFJV
112By+01Flw/X+eeN8NpqSuLV4W+AjEO8at/VvgKr1AFvBRdZ7GkpI1o6DgPe7ZqX+1
113dzQZt3e13W0rVBb/bUgx9iSLoeWP3aq/k+/GRGOR+S6F6BBSl0SQ2EF2+dIywb1x
114JuinEP+AwLAUZ1Bsx9ISC0Agpk2VeHXPL3FGhroEmoMvBzO0kTFGyoeT7PR/BfKv
115+H/g3HsL2LOB9uoIm8/5p2TTU5ttYCXMHhQZ81AY
116=AUp4
117-----END PGP PUBLIC KEY BLOCK-----
Jackeagle32d06d02019-09-18 02:43:26 -0400118"""
119
120GIT = 'git' # our git command
rohanc384dfe2020-02-19 23:30:17 -0500121MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
Jackeagle32d06d02019-09-18 02:43:26 -0400122repodir = '.repo' # name of repo's private directory
123S_repo = 'repo' # special repo repository
124S_manifests = 'manifests' # special manifest repository
125REPO_MAIN = S_repo + '/main.py' # main script
rohanc384dfe2020-02-19 23:30:17 -0500126MIN_PYTHON_VERSION = (2, 7) # minimum supported python version
Jackeagle32d06d02019-09-18 02:43:26 -0400127GITC_CONFIG_FILE = '/gitc/.config'
128GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
129
130
131import collections
132import errno
133import optparse
rohanc384dfe2020-02-19 23:30:17 -0500134import platform
Jackeagle32d06d02019-09-18 02:43:26 -0400135import re
136import shutil
137import stat
rohanc384dfe2020-02-19 23:30:17 -0500138import subprocess
139import sys
Jackeagle32d06d02019-09-18 02:43:26 -0400140
141if sys.version_info[0] == 3:
142 import urllib.request
143 import urllib.error
144else:
145 import imp
146 import urllib2
147 urllib = imp.new_module('urllib')
148 urllib.request = urllib2
149 urllib.error = urllib2
150
151
rohanc384dfe2020-02-19 23:30:17 -0500152# Python version check
153ver = sys.version_info
154if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
155 print('error: Python version {} unsupported.\n'
156 'Please use Python {}.{} instead.'.format(
157 sys.version.split(' ')[0],
158 MIN_PYTHON_VERSION[0],
159 MIN_PYTHON_VERSION[1],
160 ), file=sys.stderr)
161 sys.exit(1)
162
Jackeagle32d06d02019-09-18 02:43:26 -0400163home_dot_repo = os.path.expanduser('~/.repoconfig')
164gpg_dir = os.path.join(home_dot_repo, 'gnupg')
165
166extra_args = []
167init_optparse = optparse.OptionParser(usage="repo init -u url [options]")
168
169# Logging
170group = init_optparse.add_option_group('Logging options')
171group.add_option('-q', '--quiet',
172 dest="quiet", action="store_true", default=False,
173 help="be quiet")
174
175# Manifest
176group = init_optparse.add_option_group('Manifest options')
177group.add_option('-u', '--manifest-url',
178 dest='manifest_url',
179 help='manifest repository location', metavar='URL')
180group.add_option('-b', '--manifest-branch',
181 dest='manifest_branch',
182 help='manifest branch or revision', metavar='REVISION')
183group.add_option('-m', '--manifest-name',
184 dest='manifest_name',
185 help='initial manifest file', metavar='NAME.xml')
186group.add_option('--current-branch',
187 dest='current_branch_only', action='store_true',
188 help='fetch only current manifest branch from server')
189group.add_option('--mirror',
190 dest='mirror', action='store_true',
191 help='create a replica of the remote repositories '
192 'rather than a client working directory')
193group.add_option('--reference',
194 dest='reference',
195 help='location of mirror directory', metavar='DIR')
196group.add_option('--dissociate',
197 dest='dissociate', action='store_true',
198 help='dissociate from reference mirrors after clone')
199group.add_option('--depth', type='int', default=None,
200 dest='depth',
201 help='create a shallow clone with given depth; see git clone')
202group.add_option('--partial-clone', action='store_true',
203 dest='partial_clone',
204 help='perform partial clone (https://git-scm.com/'
205 'docs/gitrepository-layout#_code_partialclone_code)')
206group.add_option('--clone-filter', action='store', default='blob:none',
207 dest='clone_filter',
208 help='filter for use with --partial-clone [default: %default]')
209group.add_option('--archive',
210 dest='archive', action='store_true',
211 help='checkout an archive instead of a git repository for '
212 'each project. See git archive.')
213group.add_option('--submodules',
214 dest='submodules', action='store_true',
215 help='sync any submodules associated with the manifest repo')
216group.add_option('-g', '--groups',
217 dest='groups', default='default',
218 help='restrict manifest projects to ones with specified '
219 'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
220 metavar='GROUP')
221group.add_option('-p', '--platform',
222 dest='platform', default="auto",
223 help='restrict manifest projects to ones with a specified '
224 'platform group [auto|all|none|linux|darwin|...]',
225 metavar='PLATFORM')
226group.add_option('--no-clone-bundle',
227 dest='no_clone_bundle', action='store_true',
228 help='disable use of /clone.bundle on HTTP/HTTPS')
229group.add_option('--no-tags',
230 dest='no_tags', action='store_true',
231 help="don't fetch tags in the manifest")
232
233
234# Tool
235group = init_optparse.add_option_group('repo Version options')
236group.add_option('--repo-url',
237 dest='repo_url',
rohanc384dfe2020-02-19 23:30:17 -0500238 help='repo repository location', metavar='URL')
Jackeagle32d06d02019-09-18 02:43:26 -0400239group.add_option('--repo-branch',
240 dest='repo_branch',
rohanc384dfe2020-02-19 23:30:17 -0500241 help='repo branch or revision', metavar='REVISION')
Jackeagle32d06d02019-09-18 02:43:26 -0400242group.add_option('--no-repo-verify',
243 dest='no_repo_verify', action='store_true',
244 help='do not verify repo source code')
245
246# Other
247group = init_optparse.add_option_group('Other options')
248group.add_option('--config-name',
249 dest='config_name', action="store_true", default=False,
250 help='Always prompt for name/e-mail')
251
252
253def _GitcInitOptions(init_optparse_arg):
254 init_optparse_arg.set_usage("repo gitc-init -u url -c client [options]")
255 g = init_optparse_arg.add_option_group('GITC options')
256 g.add_option('-f', '--manifest-file',
257 dest='manifest_file',
258 help='Optional manifest file to use for this GITC client.')
259 g.add_option('-c', '--gitc-client',
260 dest='gitc_client',
261 help='The name of the gitc_client instance to create or modify.')
262
263_gitc_manifest_dir = None
264
265
266def get_gitc_manifest_dir():
267 global _gitc_manifest_dir
268 if _gitc_manifest_dir is None:
269 _gitc_manifest_dir = ''
270 try:
271 with open(GITC_CONFIG_FILE, 'r') as gitc_config:
272 for line in gitc_config:
273 match = re.match('gitc_dir=(?P<gitc_manifest_dir>.*)', line)
274 if match:
275 _gitc_manifest_dir = match.group('gitc_manifest_dir')
276 except IOError:
277 pass
278 return _gitc_manifest_dir
279
280
281def gitc_parse_clientdir(gitc_fs_path):
282 """Parse a path in the GITC FS and return its client name.
283
284 @param gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR.
285
286 @returns: The GITC client name
287 """
288 if gitc_fs_path == GITC_FS_ROOT_DIR:
289 return None
290 if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR):
291 manifest_dir = get_gitc_manifest_dir()
292 if manifest_dir == '':
293 return None
294 if manifest_dir[-1] != '/':
295 manifest_dir += '/'
296 if gitc_fs_path == manifest_dir:
297 return None
298 if not gitc_fs_path.startswith(manifest_dir):
299 return None
300 return gitc_fs_path.split(manifest_dir)[1].split('/')[0]
301 return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split('/')[0]
302
303
304class CloneFailure(Exception):
305
306 """Indicate the remote clone of repo itself failed.
307 """
308
309
310def _Init(args, gitc_init=False):
311 """Installs repo by cloning it over the network.
312 """
313 if gitc_init:
314 _GitcInitOptions(init_optparse)
315 opt, args = init_optparse.parse_args(args)
316 if args:
317 init_optparse.print_usage()
318 sys.exit(1)
319
320 url = opt.repo_url
321 if not url:
322 url = REPO_URL
323 extra_args.append('--repo-url=%s' % url)
324
325 branch = opt.repo_branch
326 if not branch:
327 branch = REPO_REV
328 extra_args.append('--repo-branch=%s' % branch)
329
330 if branch.startswith('refs/heads/'):
331 branch = branch[len('refs/heads/'):]
332 if branch.startswith('refs/'):
333 print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
334 raise CloneFailure()
335
336 try:
337 if gitc_init:
338 gitc_manifest_dir = get_gitc_manifest_dir()
339 if not gitc_manifest_dir:
340 print('fatal: GITC filesystem is not available. Exiting...',
341 file=sys.stderr)
342 sys.exit(1)
343 gitc_client = opt.gitc_client
344 if not gitc_client:
345 gitc_client = gitc_parse_clientdir(os.getcwd())
346 if not gitc_client:
347 print('fatal: GITC client (-c) is required.', file=sys.stderr)
348 sys.exit(1)
349 client_dir = os.path.join(gitc_manifest_dir, gitc_client)
350 if not os.path.exists(client_dir):
351 os.makedirs(client_dir)
352 os.chdir(client_dir)
353 if os.path.exists(repodir):
354 # This GITC Client has already initialized repo so continue.
355 return
356
357 os.mkdir(repodir)
358 except OSError as e:
359 if e.errno != errno.EEXIST:
360 print('fatal: cannot make %s directory: %s'
361 % (repodir, e.strerror), file=sys.stderr)
362 # Don't raise CloneFailure; that would delete the
363 # name. Instead exit immediately.
364 #
365 sys.exit(1)
366
367 _CheckGitVersion()
368 try:
rohanc384dfe2020-02-19 23:30:17 -0500369 if NeedSetupGnuPG():
370 can_verify = SetupGnuPG(opt.quiet)
Jackeagle32d06d02019-09-18 02:43:26 -0400371 else:
rohanc384dfe2020-02-19 23:30:17 -0500372 can_verify = True
Jackeagle32d06d02019-09-18 02:43:26 -0400373
374 dst = os.path.abspath(os.path.join(repodir, S_repo))
375 _Clone(url, dst, opt.quiet, not opt.no_clone_bundle)
376
rohanc384dfe2020-02-19 23:30:17 -0500377 if can_verify and not opt.no_repo_verify:
Jackeagle32d06d02019-09-18 02:43:26 -0400378 rev = _Verify(dst, branch, opt.quiet)
379 else:
380 rev = 'refs/remotes/origin/%s^0' % branch
381
382 _Checkout(dst, branch, rev, opt.quiet)
383
384 if not os.path.isfile(os.path.join(dst, 'repo')):
385 print("warning: '%s' does not look like a git-repo repository, is "
386 "REPO_URL set correctly?" % url, file=sys.stderr)
387
388 except CloneFailure:
389 if opt.quiet:
390 print('fatal: repo init failed; run without --quiet to see why',
391 file=sys.stderr)
392 raise
393
394
395# The git version info broken down into components for easy analysis.
396# Similar to Python's sys.version_info.
397GitVersion = collections.namedtuple(
398 'GitVersion', ('major', 'minor', 'micro', 'full'))
399
400def ParseGitVersion(ver_str=None):
401 if ver_str is None:
402 # Load the version ourselves.
403 ver_str = _GetGitVersion()
404
405 if not ver_str.startswith('git version '):
406 return None
407
408 full_version = ver_str[len('git version '):].strip()
409 num_ver_str = full_version.split('-')[0]
410 to_tuple = []
411 for num_str in num_ver_str.split('.')[:3]:
412 if num_str.isdigit():
413 to_tuple.append(int(num_str))
414 else:
415 to_tuple.append(0)
416 to_tuple.append(full_version)
417 return GitVersion(*to_tuple)
418
419
420def _GetGitVersion():
421 cmd = [GIT, '--version']
422 try:
423 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
424 except OSError as e:
425 print(file=sys.stderr)
426 print("fatal: '%s' is not available" % GIT, file=sys.stderr)
427 print('fatal: %s' % e, file=sys.stderr)
428 print(file=sys.stderr)
429 print('Please make sure %s is installed and in your path.' % GIT,
430 file=sys.stderr)
431 raise
432
433 ver_str = proc.stdout.read().strip()
434 proc.stdout.close()
435 proc.wait()
436 return ver_str.decode('utf-8')
437
438
439def _CheckGitVersion():
440 try:
441 ver_act = ParseGitVersion()
442 except OSError:
443 raise CloneFailure()
444
445 if ver_act is None:
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600446 print('fatal: unable to detect git version', file=sys.stderr)
Jackeagle32d06d02019-09-18 02:43:26 -0400447 raise CloneFailure()
448
449 if ver_act < MIN_GIT_VERSION:
450 need = '.'.join(map(str, MIN_GIT_VERSION))
451 print('fatal: git %s or later required' % need, file=sys.stderr)
452 raise CloneFailure()
453
454
455def NeedSetupGnuPG():
456 if not os.path.isdir(home_dot_repo):
457 return True
458
459 kv = os.path.join(home_dot_repo, 'keyring-version')
460 if not os.path.exists(kv):
461 return True
462
463 kv = open(kv).read()
464 if not kv:
465 return True
466
467 kv = tuple(map(int, kv.split('.')))
468 if kv < KEYRING_VERSION:
469 return True
470 return False
471
472
473def SetupGnuPG(quiet):
474 try:
475 os.mkdir(home_dot_repo)
476 except OSError as e:
477 if e.errno != errno.EEXIST:
478 print('fatal: cannot make %s directory: %s'
479 % (home_dot_repo, e.strerror), file=sys.stderr)
480 sys.exit(1)
481
482 try:
483 os.mkdir(gpg_dir, stat.S_IRWXU)
484 except OSError as e:
485 if e.errno != errno.EEXIST:
486 print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
487 file=sys.stderr)
488 sys.exit(1)
489
490 env = os.environ.copy()
491 try:
492 env['GNUPGHOME'] = gpg_dir
493 except UnicodeEncodeError:
494 env['GNUPGHOME'] = gpg_dir.encode()
495
496 cmd = ['gpg', '--import']
497 try:
498 proc = subprocess.Popen(cmd,
499 env=env,
500 stdin=subprocess.PIPE)
501 except OSError as e:
502 if not quiet:
503 print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
504 print('warning: Installing it is strongly encouraged.', file=sys.stderr)
505 print(file=sys.stderr)
506 return False
507
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600508 proc.stdin.write(MAINTAINER_KEYS.encode('utf-8'))
Jackeagle32d06d02019-09-18 02:43:26 -0400509 proc.stdin.close()
510
511 if proc.wait() != 0:
512 print('fatal: registering repo maintainer keys failed', file=sys.stderr)
513 sys.exit(1)
514 print()
515
rohanc384dfe2020-02-19 23:30:17 -0500516 fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
517 fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
518 fd.close()
Jackeagle32d06d02019-09-18 02:43:26 -0400519 return True
520
521
522def _SetConfig(local, name, value):
523 """Set a git configuration option to the specified value.
524 """
525 cmd = [GIT, 'config', name, value]
526 if subprocess.Popen(cmd, cwd=local).wait() != 0:
527 raise CloneFailure()
528
529
530def _InitHttp():
531 handlers = []
532
533 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
534 try:
535 import netrc
536 n = netrc.netrc()
537 for host in n.hosts:
538 p = n.hosts[host]
539 mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
540 mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
541 except:
542 pass
543 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
544 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
545
546 if 'http_proxy' in os.environ:
547 url = os.environ['http_proxy']
548 handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
549 if 'REPO_CURL_VERBOSE' in os.environ:
550 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
551 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
552 urllib.request.install_opener(urllib.request.build_opener(*handlers))
553
554
555def _Fetch(url, local, src, quiet):
556 if not quiet:
557 print('Get %s' % url, file=sys.stderr)
558
559 cmd = [GIT, 'fetch']
560 if quiet:
561 cmd.append('--quiet')
562 err = subprocess.PIPE
563 else:
564 err = None
565 cmd.append(src)
566 cmd.append('+refs/heads/*:refs/remotes/origin/*')
567 cmd.append('+refs/tags/*:refs/tags/*')
568
569 proc = subprocess.Popen(cmd, cwd=local, stderr=err)
570 if err:
571 proc.stderr.read()
572 proc.stderr.close()
573 if proc.wait() != 0:
574 raise CloneFailure()
575
576
577def _DownloadBundle(url, local, quiet):
578 if not url.endswith('/'):
579 url += '/'
580 url += 'clone.bundle'
581
582 proc = subprocess.Popen(
583 [GIT, 'config', '--get-regexp', 'url.*.insteadof'],
584 cwd=local,
585 stdout=subprocess.PIPE)
586 for line in proc.stdout:
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600587 line = line.decode('utf-8')
Jackeagle32d06d02019-09-18 02:43:26 -0400588 m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line)
589 if m:
590 new_url = m.group(1)
591 old_url = m.group(2)
592 if url.startswith(old_url):
593 url = new_url + url[len(old_url):]
594 break
595 proc.stdout.close()
596 proc.wait()
597
598 if not url.startswith('http:') and not url.startswith('https:'):
599 return False
600
601 dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b')
602 try:
603 try:
604 r = urllib.request.urlopen(url)
605 except urllib.error.HTTPError as e:
606 if e.code in [401, 403, 404, 501]:
607 return False
608 print('fatal: Cannot get %s' % url, file=sys.stderr)
609 print('fatal: HTTP error %s' % e.code, file=sys.stderr)
610 raise CloneFailure()
611 except urllib.error.URLError as e:
612 print('fatal: Cannot get %s' % url, file=sys.stderr)
613 print('fatal: error %s' % e.reason, file=sys.stderr)
614 raise CloneFailure()
615 try:
616 if not quiet:
617 print('Get %s' % url, file=sys.stderr)
618 while True:
619 buf = r.read(8192)
620 if not buf:
621 return True
622 dest.write(buf)
623 finally:
624 r.close()
625 finally:
626 dest.close()
627
628
629def _ImportBundle(local):
630 path = os.path.join(local, '.git', 'clone.bundle')
631 try:
632 _Fetch(local, local, path, True)
633 finally:
634 os.remove(path)
635
636
637def _Clone(url, local, quiet, clone_bundle):
638 """Clones a git repository to a new subdirectory of repodir
639 """
640 try:
641 os.mkdir(local)
642 except OSError as e:
643 print('fatal: cannot make %s directory: %s' % (local, e.strerror),
644 file=sys.stderr)
645 raise CloneFailure()
646
647 cmd = [GIT, 'init', '--quiet']
648 try:
649 proc = subprocess.Popen(cmd, cwd=local)
650 except OSError as e:
651 print(file=sys.stderr)
652 print("fatal: '%s' is not available" % GIT, file=sys.stderr)
653 print('fatal: %s' % e, file=sys.stderr)
654 print(file=sys.stderr)
655 print('Please make sure %s is installed and in your path.' % GIT,
656 file=sys.stderr)
657 raise CloneFailure()
658 if proc.wait() != 0:
659 print('fatal: could not create %s' % local, file=sys.stderr)
660 raise CloneFailure()
661
662 _InitHttp()
663 _SetConfig(local, 'remote.origin.url', url)
664 _SetConfig(local,
665 'remote.origin.fetch',
666 '+refs/heads/*:refs/remotes/origin/*')
667 if clone_bundle and _DownloadBundle(url, local, quiet):
668 _ImportBundle(local)
669 _Fetch(url, local, 'origin', quiet)
670
671
672def _Verify(cwd, branch, quiet):
673 """Verify the branch has been signed by a tag.
674 """
675 cmd = [GIT, 'describe', 'origin/%s' % branch]
676 proc = subprocess.Popen(cmd,
677 stdout=subprocess.PIPE,
678 stderr=subprocess.PIPE,
679 cwd=cwd)
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600680 cur = proc.stdout.read().strip().decode('utf-8')
Jackeagle32d06d02019-09-18 02:43:26 -0400681 proc.stdout.close()
682
683 proc.stderr.read()
684 proc.stderr.close()
685
686 if proc.wait() != 0 or not cur:
687 print(file=sys.stderr)
688 print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
689 raise CloneFailure()
690
691 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
692 if m:
693 cur = m.group(1)
694 if not quiet:
695 print(file=sys.stderr)
696 print("info: Ignoring branch '%s'; using tagged release '%s'"
697 % (branch, cur), file=sys.stderr)
698 print(file=sys.stderr)
699
700 env = os.environ.copy()
701 try:
702 env['GNUPGHOME'] = gpg_dir
703 except UnicodeEncodeError:
704 env['GNUPGHOME'] = gpg_dir.encode()
705
706 cmd = [GIT, 'tag', '-v', cur]
707 proc = subprocess.Popen(cmd,
708 stdout=subprocess.PIPE,
709 stderr=subprocess.PIPE,
710 cwd=cwd,
711 env=env)
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600712 out = proc.stdout.read().decode('utf-8')
Jackeagle32d06d02019-09-18 02:43:26 -0400713 proc.stdout.close()
714
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600715 err = proc.stderr.read().decode('utf-8')
Jackeagle32d06d02019-09-18 02:43:26 -0400716 proc.stderr.close()
717
718 if proc.wait() != 0:
719 print(file=sys.stderr)
720 print(out, file=sys.stderr)
721 print(err, file=sys.stderr)
722 print(file=sys.stderr)
723 raise CloneFailure()
724 return '%s^0' % cur
725
726
727def _Checkout(cwd, branch, rev, quiet):
728 """Checkout an upstream branch into the repository and track it.
729 """
730 cmd = [GIT, 'update-ref', 'refs/heads/default', rev]
731 if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
732 raise CloneFailure()
733
734 _SetConfig(cwd, 'branch.default.remote', 'origin')
735 _SetConfig(cwd, 'branch.default.merge', 'refs/heads/%s' % branch)
736
737 cmd = [GIT, 'symbolic-ref', 'HEAD', 'refs/heads/default']
738 if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
739 raise CloneFailure()
740
741 cmd = [GIT, 'read-tree', '--reset', '-u']
742 if not quiet:
743 cmd.append('-v')
744 cmd.append('HEAD')
745 if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
746 raise CloneFailure()
747
748
749def _FindRepo():
750 """Look for a repo installation, starting at the current directory.
751 """
752 curdir = os.getcwd()
753 repo = None
754
755 olddir = None
756 while curdir != '/' \
757 and curdir != olddir \
758 and not repo:
759 repo = os.path.join(curdir, repodir, REPO_MAIN)
760 if not os.path.isfile(repo):
761 repo = None
762 olddir = curdir
763 curdir = os.path.dirname(curdir)
764 return (repo, os.path.join(curdir, repodir))
765
766
767class _Options(object):
768 help = False
769
770
771def _ParseArguments(args):
772 cmd = None
773 opt = _Options()
774 arg = []
775
776 for i in range(len(args)):
777 a = args[i]
778 if a == '-h' or a == '--help':
779 opt.help = True
780
781 elif not a.startswith('-'):
782 cmd = a
783 arg = args[i + 1:]
784 break
785 return cmd, opt, arg
786
787
788def _Usage():
789 gitc_usage = ""
790 if get_gitc_manifest_dir():
791 gitc_usage = " gitc-init Initialize a GITC Client.\n"
792
793 print(
794 """usage: repo COMMAND [ARGS]
795
796repo is not yet installed. Use "repo init" to install it here.
797
798The most commonly used repo commands are:
799
800 init Install repo in the current working directory
801""" + gitc_usage +
802 """ help Display detailed help on a command
803
804For access to the full online help, install repo ("repo init").
805""")
806 sys.exit(0)
807
808
809def _Help(args):
810 if args:
811 if args[0] == 'init':
812 init_optparse.print_help()
813 sys.exit(0)
814 elif args[0] == 'gitc-init':
815 _GitcInitOptions(init_optparse)
816 init_optparse.print_help()
817 sys.exit(0)
818 else:
819 print("error: '%s' is not a bootstrap command.\n"
820 ' For access to online help, install repo ("repo init").'
821 % args[0], file=sys.stderr)
822 else:
823 _Usage()
824 sys.exit(1)
825
826
827def _NotInstalled():
828 print('error: repo is not installed. Use "repo init" to install it here.',
829 file=sys.stderr)
830 sys.exit(1)
831
832
833def _NoCommands(cmd):
834 print("""error: command '%s' requires repo to be installed first.
835 Use "repo init" to install it here.""" % cmd, file=sys.stderr)
836 sys.exit(1)
837
838
839def _RunSelf(wrapper_path):
840 my_dir = os.path.dirname(wrapper_path)
841 my_main = os.path.join(my_dir, 'main.py')
842 my_git = os.path.join(my_dir, '.git')
843
844 if os.path.isfile(my_main) and os.path.isdir(my_git):
845 for name in ['git_config.py',
846 'project.py',
847 'subcmds']:
848 if not os.path.exists(os.path.join(my_dir, name)):
849 return None, None
850 return my_main, my_git
851 return None, None
852
853
854def _SetDefaultsTo(gitdir):
855 global REPO_URL
856 global REPO_REV
857
858 REPO_URL = gitdir
859 proc = subprocess.Popen([GIT,
860 '--git-dir=%s' % gitdir,
861 'symbolic-ref',
862 'HEAD'],
863 stdout=subprocess.PIPE,
864 stderr=subprocess.PIPE)
pimpmaneaton1a90ffc2019-10-16 17:24:11 -0600865 REPO_REV = proc.stdout.read().strip().decode('utf-8')
Jackeagle32d06d02019-09-18 02:43:26 -0400866 proc.stdout.close()
867
868 proc.stderr.read()
869 proc.stderr.close()
870
871 if proc.wait() != 0:
872 print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
873 sys.exit(1)
874
875
876def main(orig_args):
877 cmd, opt, args = _ParseArguments(orig_args)
878
879 repo_main, rel_repo_dir = None, None
880 # Don't use the local repo copy, make sure to switch to the gitc client first.
881 if cmd != 'gitc-init':
882 repo_main, rel_repo_dir = _FindRepo()
883
884 wrapper_path = os.path.abspath(__file__)
885 my_main, my_git = _RunSelf(wrapper_path)
886
887 cwd = os.getcwd()
888 if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
889 print('error: repo cannot be used in the GITC local manifest directory.'
890 '\nIf you want to work on this GITC client please rerun this '
891 'command from the corresponding client under /gitc/',
892 file=sys.stderr)
893 sys.exit(1)
894 if not repo_main:
895 if opt.help:
896 _Usage()
897 if cmd == 'help':
898 _Help(args)
899 if not cmd:
900 _NotInstalled()
901 if cmd == 'init' or cmd == 'gitc-init':
902 if my_git:
903 _SetDefaultsTo(my_git)
904 try:
905 _Init(args, gitc_init=(cmd == 'gitc-init'))
906 except CloneFailure:
907 path = os.path.join(repodir, S_repo)
908 print("fatal: cloning the git-repo repository failed, will remove "
909 "'%s' " % path, file=sys.stderr)
910 shutil.rmtree(path, ignore_errors=True)
911 sys.exit(1)
912 repo_main, rel_repo_dir = _FindRepo()
913 else:
914 _NoCommands(cmd)
915
916 if my_main:
917 repo_main = my_main
918
919 ver_str = '.'.join(map(str, VERSION))
920 me = [sys.executable, repo_main,
921 '--repo-dir=%s' % rel_repo_dir,
922 '--wrapper-version=%s' % ver_str,
923 '--wrapper-path=%s' % wrapper_path,
924 '--']
925 me.extend(orig_args)
926 me.extend(extra_args)
rohanc384dfe2020-02-19 23:30:17 -0500927 try:
928 if platform.system() == "Windows":
929 sys.exit(subprocess.call(me))
930 else:
931 os.execv(sys.executable, me)
932 except OSError as e:
933 print("fatal: unable to start %s" % repo_main, file=sys.stderr)
934 print("fatal: %s" % e, file=sys.stderr)
935 sys.exit(148)
Jackeagle32d06d02019-09-18 02:43:26 -0400936
937
938if __name__ == '__main__':
939 main(sys.argv[1:])