Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
uses: fedora-python/tox-github-action@master
with:
tox_env: ${{ matrix.tox_env }}
dnf_install: /usr/bin/rpmdev-bumpspec
strategy:
matrix:
tox_env: [py38, py39]
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ ferrypick

Apply patches from Fedora dist git to different components.

This simple tool does 3 steps:
This simple tool does these steps:

1. download patch file from src.fedoraproject.org
2. replaces package name with current dist-git work dir package name
3. runs `git am --reject` on the product
4. if the rejected hunks only touch `Release` and add `%changelog` in spec
files, ignore the rejects and run `rpmdev-bumpspec` instead.
(This step is skipped if there's a chance it wouldn't be safe,
such if there are as pre-existing `.rej` files.)

Requires the `rpmdev-bumpspec` tool from [rpmdevtools].

[rpmdevtools]: https://pagure.io/rpmdevtools

Usage:

Expand Down
98 changes: 90 additions & 8 deletions ferrypick.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import subprocess
import sys
import urllib.request
import shlex
from pathlib import Path

COMMIT_RE = re.compile(r"^https://src\.fedoraproject\.org/\S+/([^/\s]+)/c/([0-9a-f]+)")
PR_RE = re.compile(r"^https://src\.fedoraproject\.org/\S+/([^/\s]+)/pull-request/\d+")
Expand Down Expand Up @@ -64,9 +66,9 @@ def stdout(cmd):
return subprocess.check_output(cmd, shell=True, text=True).rstrip()


def execute(cmd):
proc = subprocess.run(cmd, shell=True, text=True)
return proc.returncode
def execute(*cmd, **kwargs):
print(f"$ {' '.join(shlex.quote(str(c)) for c in cmd)}")
return subprocess.run(cmd, text=True, **kwargs)


def parse_args():
Expand Down Expand Up @@ -97,15 +99,95 @@ def get_patch_content(link):
return (content, original_name)


def handle_reject(filename):
"""If the .rej file given in `filename` is "simple", run rmpdev-bumpspec

Simple means roughly that only Release lines are touched and
%changelog lines are added.

Removes the reject file if successful.
"""
changelog = None
path = Path(filename)
author = None
with path.open() as f:
for line in f:
# Find first hunk header
if line.startswith('@'):
break
for line in f:
marker = line[:1]
print(line.rstrip())
if marker == '@':
# Hunk header
continue
elif marker == ' ':
# Context
if line.strip() == '%changelog':
changelog = []
continue
elif marker in ('+', '-'):
if changelog is not None:
if marker == '-':
# Removing existing changelog - bad
return
if match := re.match(
r'\*\s+(\S+\s+){4}(?P<author>[^>]+>)',
line[1:]
):
author = match['author']
else:
changelog.append(line[1:])
elif line[1:].startswith('Release:'):
continue
else:
# Adding/removing something else - bad
return
else:
# Unknown line - bad
return
if author is None:
print('No author found in reject')
return
print(f'Rejects in {filename} look harmless')
execute(
'rpmdev-bumpspec',
'-u', author,
'-c', ''.join(changelog).strip(),
path.with_suffix(''),
check=True,
)
path.unlink()


def apply_patch(filename):
cmd = f"git am --committer-date-is-author-date --reject {filename}"
print(f"$ {cmd}")
exitcode = execute(cmd)
args = [
"git", "am", "--committer-date-is-author-date", "--reject", filename,
]
previous_rej = any(Path().glob(f'**/*.rej'))
exitcode = execute(*args).returncode
if exitcode:
if previous_rej:
print(
"Not attempting to process rejected patches: "
+ "There were pre-existing *.rej files in your worktree.",
file=sys.stderr
)
sys.exit(exitcode)
print(file=sys.stderr)
print(f"{cmd} failed with exit code {exitcode}", file=sys.stderr)
print(f"git am failed with exit code {exitcode}", file=sys.stderr)
print(f"Patch stored as: {filename}", file=sys.stderr)
sys.exit(exitcode)

for spec_rej in Path().glob(f'**/*.spec.rej'):
print(f'Processing rejects in {spec_rej}')
handle_reject(spec_rej)
if not any(Path().glob(f'**/*.rej')):
for spec in Path().glob(f'**/*.spec'):
execute("git", "add", spec.relative_to(Path()), check=True)
exitcode = execute("git", "am", "--continue").returncode

if exitcode:
sys.exit(exitcode)


def main():
Expand Down
Loading