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