mirror of
https://git.yoctoproject.org/git/poky
synced 2026-01-01 13:58:04 +00:00
When git is configured with safe.bareRepository=explicit [1], the git-make-shallow fails miserably. LWN has an article about the problem that this configuration option addresses and why it is useful in [2]. It also seems that it is being rolled out in some environments as a default for users. In order to allow having this configuration turned on for a user's environment in general, the fetcher has to be tought to use --git-dir= for all relevent git operations. The alternative, implemented here, is to forcibly turn off that option for all git operations. In the future, we could look into converting these to using the --git-dir= command line argument instead. Link: https://git.kernel.org/pub/scm/git/git.git/tree/Documentation/config/safe.txt#n1 [1] Link: https://lwn.net/Articles/892755/ [2] (Bitbake rev: 7c63989db4590564516ed150930f4e2fa503e98f) Signed-off-by: André Draszik <andre.draszik@linaro.org> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
176 lines
5.9 KiB
Python
Executable File
176 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright BitBake Contributors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
#
|
|
|
|
"""git-make-shallow: make the current git repository shallow
|
|
|
|
Remove the history of the specified revisions, then optionally filter the
|
|
available refs to those specified.
|
|
"""
|
|
|
|
import argparse
|
|
import collections
|
|
import errno
|
|
import itertools
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import warnings
|
|
warnings.simplefilter("default")
|
|
|
|
version = 1.0
|
|
|
|
|
|
git_cmd = ['git', '-c', 'safe.bareRepository=all']
|
|
|
|
def main():
|
|
if sys.version_info < (3, 4, 0):
|
|
sys.exit('Python 3.4 or greater is required')
|
|
|
|
git_dir = check_output(git_cmd + ['rev-parse', '--git-dir']).rstrip()
|
|
shallow_file = os.path.join(git_dir, 'shallow')
|
|
if os.path.exists(shallow_file):
|
|
try:
|
|
check_output(git_cmd + ['fetch', '--unshallow'])
|
|
except subprocess.CalledProcessError:
|
|
try:
|
|
os.unlink(shallow_file)
|
|
except OSError as exc:
|
|
if exc.errno != errno.ENOENT:
|
|
raise
|
|
|
|
args = process_args()
|
|
revs = check_output(git_cmd + ['rev-list'] + args.revisions).splitlines()
|
|
|
|
make_shallow(shallow_file, args.revisions, args.refs)
|
|
|
|
ref_revs = check_output(git_cmd + ['rev-list'] + args.refs).splitlines()
|
|
remaining_history = set(revs) & set(ref_revs)
|
|
for rev in remaining_history:
|
|
if check_output(git_cmd + ['rev-parse', '{}^@'.format(rev)]):
|
|
sys.exit('Error: %s was not made shallow' % rev)
|
|
|
|
filter_refs(args.refs)
|
|
|
|
if args.shrink:
|
|
shrink_repo(git_dir)
|
|
subprocess.check_call(git_cmd + ['fsck', '--unreachable'])
|
|
|
|
|
|
def process_args():
|
|
# TODO: add argument to automatically keep local-only refs, since they
|
|
# can't be easily restored with a git fetch.
|
|
parser = argparse.ArgumentParser(description='Remove the history of the specified revisions, then optionally filter the available refs to those specified.')
|
|
parser.add_argument('--ref', '-r', metavar='REF', action='append', dest='refs', help='remove all but the specified refs (cumulative)')
|
|
parser.add_argument('--shrink', '-s', action='store_true', help='shrink the git repository by repacking and pruning')
|
|
parser.add_argument('revisions', metavar='REVISION', nargs='+', help='a git revision/commit')
|
|
if len(sys.argv) < 2:
|
|
parser.print_help()
|
|
sys.exit(2)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.refs:
|
|
args.refs = check_output(git_cmd + ['rev-parse', '--symbolic-full-name'] + args.refs).splitlines()
|
|
else:
|
|
args.refs = get_all_refs(lambda r, t, tt: t == 'commit' or tt == 'commit')
|
|
|
|
args.refs = list(filter(lambda r: not r.endswith('/HEAD'), args.refs))
|
|
args.revisions = check_output(git_cmd + ['rev-parse'] + ['%s^{}' % i for i in args.revisions]).splitlines()
|
|
return args
|
|
|
|
|
|
def check_output(cmd, input=None):
|
|
return subprocess.check_output(cmd, universal_newlines=True, input=input)
|
|
|
|
|
|
def make_shallow(shallow_file, revisions, refs):
|
|
"""Remove the history of the specified revisions."""
|
|
for rev in follow_history_intersections(revisions, refs):
|
|
print("Processing %s" % rev)
|
|
with open(shallow_file, 'a') as f:
|
|
f.write(rev + '\n')
|
|
|
|
|
|
def get_all_refs(ref_filter=None):
|
|
"""Return all the existing refs in this repository, optionally filtering the refs."""
|
|
ref_output = check_output(git_cmd + ['for-each-ref', '--format=%(refname)\t%(objecttype)\t%(*objecttype)'])
|
|
ref_split = [tuple(iter_extend(l.rsplit('\t'), 3)) for l in ref_output.splitlines()]
|
|
if ref_filter:
|
|
ref_split = (e for e in ref_split if ref_filter(*e))
|
|
refs = [r[0] for r in ref_split]
|
|
return refs
|
|
|
|
|
|
def iter_extend(iterable, length, obj=None):
|
|
"""Ensure that iterable is the specified length by extending with obj."""
|
|
return itertools.islice(itertools.chain(iterable, itertools.repeat(obj)), length)
|
|
|
|
|
|
def filter_refs(refs):
|
|
"""Remove all but the specified refs from the git repository."""
|
|
all_refs = get_all_refs()
|
|
to_remove = set(all_refs) - set(refs)
|
|
if to_remove:
|
|
check_output(['xargs', '-0', '-n', '1'] + git_cmd + ['update-ref', '-d', '--no-deref'],
|
|
input=''.join(l + '\0' for l in to_remove))
|
|
|
|
|
|
def follow_history_intersections(revisions, refs):
|
|
"""Determine all the points where the history of the specified revisions intersects the specified refs."""
|
|
queue = collections.deque(revisions)
|
|
seen = set()
|
|
|
|
for rev in iter_except(queue.popleft, IndexError):
|
|
if rev in seen:
|
|
continue
|
|
|
|
parents = check_output(git_cmd + ['rev-parse', '%s^@' % rev]).splitlines()
|
|
|
|
yield rev
|
|
seen.add(rev)
|
|
|
|
if not parents:
|
|
continue
|
|
|
|
check_refs = check_output(git_cmd + ['merge-base', '--independent'] + sorted(refs)).splitlines()
|
|
for parent in parents:
|
|
for ref in check_refs:
|
|
print("Checking %s vs %s" % (parent, ref))
|
|
try:
|
|
merge_base = check_output(git_cmd + ['merge-base', parent, ref]).rstrip()
|
|
except subprocess.CalledProcessError:
|
|
continue
|
|
else:
|
|
queue.append(merge_base)
|
|
|
|
|
|
def iter_except(func, exception, start=None):
|
|
"""Yield a function repeatedly until it raises an exception."""
|
|
try:
|
|
if start is not None:
|
|
yield start()
|
|
while True:
|
|
yield func()
|
|
except exception:
|
|
pass
|
|
|
|
|
|
def shrink_repo(git_dir):
|
|
"""Shrink the newly shallow repository, removing the unreachable objects."""
|
|
subprocess.check_call(git_cmd + ['reflog', 'expire', '--expire-unreachable=now', '--all'])
|
|
subprocess.check_call(git_cmd + ['repack', '-ad'])
|
|
try:
|
|
os.unlink(os.path.join(git_dir, 'objects', 'info', 'alternates'))
|
|
except OSError as exc:
|
|
if exc.errno != errno.ENOENT:
|
|
raise
|
|
subprocess.check_call(git_cmd + ['prune', '--expire', 'now'])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|