init: clone the manifest repository, don't fetch it

West init without --mr is leaving the manifest repository in a
detached HEAD state.

Fixes: #522

The convoluted logic we use to "clone" the manifest repository using
init and fetch is only in place to allow using a magic GitHub pull
request ref as --manifest-rev. This small benefit is no longer worth
the downsides.

To fix it, just use 'git clone' instead. This also lets us
drop a bunch of extra logic trying to figure out what the remote wants
us to use by default.

It also avoids getting the now-infamous "Using 'master' as the name
for the initial branch." message on the first west init if the user
doesn't have init.defaultBranch set.

Preserve 'west init --manifest-rev foo' using 'git clone --branch
foo'. This works fine with branches or tags, but you'll no longer be
able to initialize a workspace from a 'pull/1234/head' GitHub pull
request reference or a SHA. Affected users are going to need to do
'west init', then fetch the revision they need before running 'west
update'.

Unlike other branch related options (like 'git init --initial
branch'), we've had 'git clone --branch' since somewhere in the git
v1.x days, so it's safe to use unconditionally.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Martí Bolívar 2021-07-23 16:23:59 -07:00 committed by Marti Bolivar
parent 49b8695f68
commit e283d9986f
1 changed files with 15 additions and 84 deletions

View File

@ -9,7 +9,7 @@ import argparse
from functools import partial
import logging
import os
from os.path import abspath, relpath, exists
from os.path import abspath, relpath
from pathlib import PurePath, Path
import multiprocessing
import shutil
@ -239,7 +239,11 @@ With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
log.banner('Initializing in', topdir)
manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT
manifest_rev = args.manifest_rev or self.get_head_branch(manifest_url)
if args.manifest_rev:
# This works with tags, too.
branch_opt = ['--branch', args.manifest_rev]
else:
branch_opt = []
west_dir = topdir / WEST_DIR
try:
@ -257,7 +261,12 @@ With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
log.dbg('removing existing temporary manifest directory', tempdir)
shutil.rmtree(tempdir)
try:
self.clone_manifest(manifest_url, manifest_rev, os.fspath(tempdir))
log.small_banner(
f'Cloning manifest repository from {manifest_url}' +
(f', rev. {args.manifest_rev}' if args.manifest_rev else ''))
self.check_call(['git', 'clone'] + branch_opt +
[manifest_url, os.fspath(tempdir)])
except subprocess.CalledProcessError:
shutil.rmtree(tempdir, ignore_errors=True)
raise
@ -268,8 +277,9 @@ With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
if not temp_manifest.is_file():
log.die(f'can\'t init: no {temp_manifest_filename} found in '
f'{tempdir}\n'
f' Hint: check --manifest-url={manifest_url} and '
f'--manifest-rev={manifest_rev}\n'
f' Hint: check --manifest-url={manifest_url}' +
(f' and --manifest-rev={args.manifest_rev}'
if args.manifest_rev else '') +
f' You may need to remove {west_dir} before retrying.')
# Parse the manifest to get the manifest path, if it declares one.
@ -314,85 +324,6 @@ With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
except Exception as e:
log.die(f"Can't create {directory}: {e}")
def get_head_branch(self, url: str) -> str:
# Get the branch which url's HEAD points to. Errors out if it
# can't, prints a banner if it can.
if self.git_version_info < (2, 8, 0):
# This recipe requires git 2.8.0 or later. Fall back
# if we're running on something that's too old.
return 'master'
err_msg = (f'failed getting the default branch from {url}; '
'please provide the --manifest-rev option')
# The '--quiet' option disables printing the URL to stderr.
try:
output = self.check_output(
('git', 'ls-remote', '--quiet', '--symref', url, 'HEAD')
).decode('utf-8')
except subprocess.CalledProcessError:
log.die(err_msg)
for line in output.splitlines():
if not line.startswith('ref: '):
continue
# The output looks like this:
#
# ref: refs/heads/foo HEAD
# 6145ab537fcb3adc3ee77db5f5f95e661f1e91e6 HEAD
#
# So this is the 'ref: ...' case.
#
# Per git-check-ref-format(1), references can't have tabs
# in them, so this doesn't have any weird edge cases.
without_ref = line[len('ref: '):]
if not without_ref:
continue
ret = without_ref.split('\t')[0]
log.small_banner('no --manifest-rev was given; '
f"using remote's default branch: {ret}")
return ret
log.die(err_msg)
def clone_manifest(self, url: str, rev: str, dest: str,
exist_ok=False) -> None:
log.small_banner(f'Cloning manifest repository from {url}, rev. {rev}')
if not exist_ok and exists(dest):
log.die(f'refusing to clone into existing location {dest}')
self.check_call(('git', 'init', dest))
self.check_call(('git', 'remote', 'add', 'origin', '--', url),
cwd=dest)
maybe_sha = _maybe_sha(rev)
if maybe_sha:
# Fetch the ref-space and hope the SHA is contained in
# that ref-space
self.check_call(('git', 'fetch', 'origin', '--tags',
'--', 'refs/heads/*:refs/remotes/origin/*'),
cwd=dest)
else:
# Fetch the ref-space similar to git clone plus the ref
# given by user. Redundancy is ok, for example if the user
# specifies 'heads/my-branch'. This allows users to specify:
# pull/<no>/head for pull requests
self.check_call(('git', 'fetch', 'origin', '--tags', '--',
rev, 'refs/heads/*:refs/remotes/origin/*'),
cwd=dest)
try:
# Using show-ref to determine if rev is available in local repo.
self.check_call(('git', 'show-ref', '--', rev), cwd=dest)
local_rev = True
except subprocess.CalledProcessError:
local_rev = False
if local_rev or maybe_sha:
self.check_call(('git', 'checkout', rev), cwd=dest)
else:
self.check_call(('git', 'checkout', 'FETCH_HEAD'), cwd=dest)
class List(_ProjectCommand):
def __init__(self):
super().__init__(