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