|
3 | 3 | import subprocess |
4 | 4 | import sys |
5 | 5 | import urllib.request |
| 6 | +import shlex |
| 7 | +from pathlib import Path |
6 | 8 |
|
7 | 9 | COMMIT_RE = re.compile(r"^https://src\.fedoraproject\.org/\S+/([^/\s]+)/c/([0-9a-f]+)") |
8 | 10 | PR_RE = re.compile(r"^https://src\.fedoraproject\.org/\S+/([^/\s]+)/pull-request/\d+") |
@@ -62,9 +64,9 @@ def stdout(cmd): |
62 | 64 | return subprocess.check_output(cmd, shell=True, text=True).rstrip() |
63 | 65 |
|
64 | 66 |
|
65 | | -def execute(cmd): |
66 | | - proc = subprocess.run(cmd, shell=True, text=True) |
67 | | - return proc.returncode |
| 67 | +def execute(*cmd, **kwargs): |
| 68 | + print(f"$ {' '.join(shlex.quote(str(c)) for c in cmd)}") |
| 69 | + return subprocess.run(cmd, text=True, **kwargs) |
68 | 70 |
|
69 | 71 |
|
70 | 72 | def parse_args(): |
@@ -95,15 +97,84 @@ def get_patch_content(link): |
95 | 97 | return (content, original_name) |
96 | 98 |
|
97 | 99 |
|
| 100 | +def handle_reject(filename): |
| 101 | + """If the .rej file given in `filename` is "simple", run rmpdev-bumpspec |
| 102 | +
|
| 103 | + Simple means roughly that only Release lines are touched and |
| 104 | + %changelog lines are added. |
| 105 | +
|
| 106 | + Removes the reject file if successful. |
| 107 | + """ |
| 108 | + changelog = None |
| 109 | + path = Path(filename) |
| 110 | + author = None |
| 111 | + with path.open() as f: |
| 112 | + for line in f: |
| 113 | + # Find first hunk header |
| 114 | + if line.startswith('@'): |
| 115 | + break |
| 116 | + for line in f: |
| 117 | + marker = line[:1] |
| 118 | + print(line.rstrip()) |
| 119 | + if marker == '@': |
| 120 | + # Hunk header |
| 121 | + continue |
| 122 | + elif marker == ' ': |
| 123 | + # Context |
| 124 | + if line.strip() == '%changelog': |
| 125 | + changelog = [] |
| 126 | + continue |
| 127 | + elif marker in ('+', '-'): |
| 128 | + if changelog is not None: |
| 129 | + if marker == '-': |
| 130 | + # Removing existing changelog - bad |
| 131 | + return |
| 132 | + if match := re.match( |
| 133 | + r'\*\s+(\S+\s+){4}(?P<author>[^>]+>)', |
| 134 | + line[1:] |
| 135 | + ): |
| 136 | + author = match['author'] |
| 137 | + else: |
| 138 | + changelog.append(line[1:]) |
| 139 | + elif line[1:].startswith('Release:'): |
| 140 | + continue |
| 141 | + else: |
| 142 | + # Adding/removing something else - bad |
| 143 | + return |
| 144 | + else: |
| 145 | + # Unknown line - bad |
| 146 | + return |
| 147 | + if author is None: |
| 148 | + print('No author found in reject') |
| 149 | + return |
| 150 | + print(f'Rejects in {filename} look harmless') |
| 151 | + execute( |
| 152 | + 'rpmdev-bumpspec', |
| 153 | + '-u', author, |
| 154 | + '-c', ''.join(changelog).strip(), |
| 155 | + path.with_suffix(''), |
| 156 | + check=True, |
| 157 | + ) |
| 158 | + path.unlink() |
| 159 | + |
| 160 | + |
98 | 161 | def apply_patch(filename): |
99 | | - cmd = f"git am --reject {filename}" |
100 | | - print(f"$ {cmd}") |
101 | | - exitcode = execute(cmd) |
| 162 | + exitcode = execute("git", "am", "--reject", filename).returncode |
102 | 163 | if exitcode: |
103 | 164 | print(file=sys.stderr) |
104 | | - print(f"{cmd} failed with exit code {exitcode}", file=sys.stderr) |
| 165 | + print(f"git am failed with exit code {exitcode}", file=sys.stderr) |
105 | 166 | print(f"Patch stored as: {filename}", file=sys.stderr) |
106 | | - sys.exit(exitcode) |
| 167 | + |
| 168 | + for spec_rej in Path().glob(f'**/*.spec.rej'): |
| 169 | + print(f'Processing rejects in {spec_rej}') |
| 170 | + handle_reject(spec_rej) |
| 171 | + if not any(Path().glob(f'**/*.rej')): |
| 172 | + for spec in Path().glob(f'**/*.spec'): |
| 173 | + execute("git", "add", spec.relative_to(Path()), check=True) |
| 174 | + exitcode = execute("git", "am", "--continue").returncode |
| 175 | + |
| 176 | + if exitcode: |
| 177 | + sys.exit(exitcode) |
107 | 178 |
|
108 | 179 |
|
109 | 180 | def main(): |
|
0 commit comments