feat: allow custom refs for patch import & export (#41306)
* feat: allow custom refs for patch import & export feat: add Patch-Dir metainfo, a sibling to Patch-Filename * chore: copyediting * refactor: minor copyediting
This commit is contained in:
parent
5f785f213e
commit
6a616ab70c
2 changed files with 37 additions and 42 deletions
|
@ -13,6 +13,9 @@ import re
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
from .patches import PATCH_FILENAME_PREFIX, is_patch_location_line
|
||||
|
||||
UPSTREAM_HEAD='refs/patches/upstream-head'
|
||||
|
||||
def is_repo_root(path):
|
||||
path_exists = os.path.exists(path)
|
||||
|
@ -75,14 +78,10 @@ def am(repo, patch_data, threeway=False, directory=None, exclude=None,
|
|||
proc.returncode))
|
||||
|
||||
|
||||
def import_patches(repo, **kwargs):
|
||||
def import_patches(repo, ref=UPSTREAM_HEAD, **kwargs):
|
||||
"""same as am(), but we save the upstream HEAD so we can refer to it when we
|
||||
later export patches"""
|
||||
update_ref(
|
||||
repo=repo,
|
||||
ref='refs/patches/upstream-head',
|
||||
newvalue='HEAD'
|
||||
)
|
||||
update_ref(repo=repo, ref=ref, newvalue='HEAD')
|
||||
am(repo=repo, **kwargs)
|
||||
|
||||
|
||||
|
@ -92,32 +91,18 @@ def update_ref(repo, ref, newvalue):
|
|||
return subprocess.check_call(args)
|
||||
|
||||
|
||||
def get_upstream_head(repo):
|
||||
args = [
|
||||
'git',
|
||||
'-C',
|
||||
repo,
|
||||
'rev-parse',
|
||||
'--verify',
|
||||
'refs/patches/upstream-head',
|
||||
]
|
||||
def get_commit_for_ref(repo, ref):
|
||||
args = ['git', '-C', repo, 'rev-parse', '--verify', ref]
|
||||
return subprocess.check_output(args).decode('utf-8').strip()
|
||||
|
||||
def get_commit_count(repo, commit_range):
|
||||
args = [
|
||||
'git',
|
||||
'-C',
|
||||
repo,
|
||||
'rev-list',
|
||||
'--count',
|
||||
commit_range
|
||||
]
|
||||
args = ['git', '-C', repo, 'rev-list', '--count', commit_range]
|
||||
return int(subprocess.check_output(args).decode('utf-8').strip())
|
||||
|
||||
def guess_base_commit(repo):
|
||||
def guess_base_commit(repo, ref):
|
||||
"""Guess which commit the patches might be based on"""
|
||||
try:
|
||||
upstream_head = get_upstream_head(repo)
|
||||
upstream_head = get_commit_for_ref(repo, ref)
|
||||
num_commits = get_commit_count(repo, upstream_head + '..')
|
||||
return [upstream_head, num_commits]
|
||||
except subprocess.CalledProcessError:
|
||||
|
@ -149,7 +134,6 @@ def format_patch(repo, since):
|
|||
'format-patch',
|
||||
'--keep-subject',
|
||||
'--no-stat',
|
||||
'--notes',
|
||||
'--stdout',
|
||||
|
||||
# Per RFC 3676 the signature is separated from the body by a line with
|
||||
|
@ -204,8 +188,8 @@ def get_file_name(patch):
|
|||
"""Return the name of the file to which the patch should be written"""
|
||||
file_name = None
|
||||
for line in patch:
|
||||
if line.startswith('Patch-Filename: '):
|
||||
file_name = line[len('Patch-Filename: '):]
|
||||
if line.startswith(PATCH_FILENAME_PREFIX):
|
||||
file_name = line[len(PATCH_FILENAME_PREFIX):]
|
||||
break
|
||||
# If no patch-filename header, munge the subject.
|
||||
if not file_name:
|
||||
|
@ -218,19 +202,18 @@ def get_file_name(patch):
|
|||
|
||||
def join_patch(patch):
|
||||
"""Joins and formats patch contents"""
|
||||
return ''.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
|
||||
return ''.join(remove_patch_location(patch)).rstrip('\n') + '\n'
|
||||
|
||||
|
||||
def remove_patch_filename(patch):
|
||||
"""Strip out the Patch-Filename trailer from a patch's message body"""
|
||||
def remove_patch_location(patch):
|
||||
"""Strip out the patch location lines from a patch's message body"""
|
||||
force_keep_next_line = False
|
||||
n = len(patch)
|
||||
for i, l in enumerate(patch):
|
||||
is_patchfilename = l.startswith('Patch-Filename: ')
|
||||
next_is_patchfilename = i < len(patch) - 1 and patch[i + 1].startswith(
|
||||
'Patch-Filename: '
|
||||
)
|
||||
skip_line = is_patch_location_line(l)
|
||||
skip_next = i < n - 1 and is_patch_location_line(patch[i + 1])
|
||||
if not force_keep_next_line and (
|
||||
is_patchfilename or (next_is_patchfilename and len(l.rstrip()) == 0)
|
||||
skip_line or (skip_next and len(l.rstrip()) == 0)
|
||||
):
|
||||
pass # drop this line
|
||||
else:
|
||||
|
@ -238,20 +221,24 @@ def remove_patch_filename(patch):
|
|||
force_keep_next_line = l.startswith('Subject: ')
|
||||
|
||||
|
||||
def export_patches(repo, out_dir, patch_range=None, dry_run=False, grep=None):
|
||||
def export_patches(repo, out_dir,
|
||||
patch_range=None, ref=UPSTREAM_HEAD,
|
||||
dry_run=False, grep=None):
|
||||
if not os.path.exists(repo):
|
||||
sys.stderr.write(
|
||||
"Skipping patches in {} because it does not exist.\n".format(repo)
|
||||
)
|
||||
return
|
||||
if patch_range is None:
|
||||
patch_range, num_patches = guess_base_commit(repo)
|
||||
patch_range, num_patches = guess_base_commit(repo, ref)
|
||||
sys.stderr.write("Exporting {} patches in {} since {}\n".format(
|
||||
num_patches, repo, patch_range[0:7]))
|
||||
patch_data = format_patch(repo, patch_range)
|
||||
patches = split_patches(patch_data)
|
||||
if grep:
|
||||
olen = len(patches)
|
||||
patches = filter_patches(patches, grep)
|
||||
sys.stderr.write("Exporting {} of {} patches\n".format(len(patches), olen))
|
||||
|
||||
try:
|
||||
os.mkdir(out_dir)
|
||||
|
|
|
@ -3,19 +3,27 @@
|
|||
import codecs
|
||||
import os
|
||||
|
||||
PATCH_DIR_PREFIX = "Patch-Dir: "
|
||||
PATCH_FILENAME_PREFIX = "Patch-Filename: "
|
||||
PATCH_LINE_PREFIXES = (PATCH_DIR_PREFIX, PATCH_FILENAME_PREFIX)
|
||||
|
||||
|
||||
def is_patch_location_line(line):
|
||||
return line.startswith(PATCH_LINE_PREFIXES)
|
||||
|
||||
def read_patch(patch_dir, patch_filename):
|
||||
"""Read a patch from |patch_dir/filename| and amend the commit message with
|
||||
metadata about the patch file it came from."""
|
||||
ret = []
|
||||
added_filename_line = False
|
||||
added_patch_location = False
|
||||
patch_path = os.path.join(patch_dir, patch_filename)
|
||||
with codecs.open(patch_path, encoding='utf-8') as f:
|
||||
for l in f.readlines():
|
||||
line_has_correct_start = l.startswith('diff -') or l.startswith('---')
|
||||
if not added_filename_line and line_has_correct_start:
|
||||
ret.append('Patch-Filename: {}\n'.format(patch_filename))
|
||||
added_filename_line = True
|
||||
if not added_patch_location and line_has_correct_start:
|
||||
ret.append('{}{}\n'.format(PATCH_DIR_PREFIX, patch_dir))
|
||||
ret.append('{}{}\n'.format(PATCH_FILENAME_PREFIX, patch_filename))
|
||||
added_patch_location = True
|
||||
ret.append(l)
|
||||
return ''.join(ret)
|
||||
|
||||
|
|
Loading…
Reference in a new issue