Skip to content

Commit 45b4a2b

Browse files
TomasTomeceknforro
andcommitted
backporting accepts a list of patches
for standalone, you now need to pass UPSTREAM_PATCHES and not UPSTREAM_FIX Resolves packit/jotnar#109 Signed-off-by: Tomas Tomecek <[email protected]> Co-authored-by: Nikola Forró <[email protected]> Assisted-by: Cursor(Claude)
1 parent c5ecf16 commit 45b4a2b

File tree

4 files changed

+52
-32
lines changed

4 files changed

+52
-32
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ run-rebase-agent-standalone: run-rebase-agent-c10s-standalone
5959
run-backport-agent-c9s-standalone:
6060
$(COMPOSE_AGENTS) run --rm \
6161
-e PACKAGE=$(PACKAGE) \
62-
-e UPSTREAM_FIX=$(UPSTREAM_FIX) \
62+
-e UPSTREAM_PATCHES=$(UPSTREAM_PATCHES) \
6363
-e JIRA_ISSUE=$(JIRA_ISSUE) \
6464
-e BRANCH=$(BRANCH) \
6565
-e DRY_RUN=$(DRY_RUN) \
@@ -70,7 +70,7 @@ run-backport-agent-c9s-standalone:
7070
run-backport-agent-c10s-standalone:
7171
$(COMPOSE_AGENTS) run --rm \
7272
-e PACKAGE=$(PACKAGE) \
73-
-e UPSTREAM_FIX=$(UPSTREAM_FIX) \
73+
-e UPSTREAM_PATCHES=$(UPSTREAM_PATCHES) \
7474
-e JIRA_ISSUE=$(JIRA_ISSUE) \
7575
-e BRANCH=$(BRANCH) \
7676
-e DRY_RUN=$(DRY_RUN) \

README-agents.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@ make trigger-pipeline JIRA_ISSUE=RHEL-12345
7373
# Test specific agents standalone
7474
make JIRA_ISSUE=RHEL-12345 run-triage-agent-standalone
7575
make PACKAGE=httpd VERSION=2.4.62 JIRA_ISSUE=RHEL-12345 BRANCH=c10s run-rebase-agent-standalone
76-
make PACKAGE=httpd UPSTREAM_FIX=https://github.com/... JIRA_ISSUE=RHEL-12345 BRANCH=c10s run-backport-agent-standalone
76+
make PACKAGE=httpd UPSTREAM_PATCHES=https://github.com/... JIRA_ISSUE=RHEL-12345 BRANCH=c10s run-backport-agent-standalone
7777

7878
# Or with dry-run
7979
DRY_RUN=true make JIRA_ISSUE=RHEL-12345 run-triage-agent-standalone
8080
```
8181

82+
Use commas to delimit multiple patch/commit URLs in `UPSTREAM_PATCHES`.
83+
8284
**Monitoring:**
8385
- Phoenix tracing: http://localhost:6006/
8486
- Redis queue monitoring: http://localhost:8081/

agents/backport_agent.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def get_instructions() -> str:
8686
return """
8787
You are an expert on backporting upstream patches to packages in RHEL ecosystem.
8888
89-
To backport upstream fix <UPSTREAM_FIX> to package <PACKAGE> in dist-git branch <DIST_GIT_BRANCH>, do the following:
89+
To backport upstream patches <UPSTREAM_PATCHES> to package <PACKAGE> in dist-git branch <DIST_GIT_BRANCH>, do the following:
9090
9191
CRITICAL: Do NOT modify, delete, or touch any existing patches in the dist-git repository.
9292
Only add new patches for the current backport. Existing patches are there for a reason
@@ -97,7 +97,7 @@ def get_instructions() -> str:
9797
end the process with `success=True` and `status="Backport already applied"`.
9898
9999
2. Use the `git_prepare_package_sources` tool to prepare package sources in directory <UNPACKED_SOURCES>
100-
for application of the upstream fix.
100+
for application of the upstream patch.
101101
102102
3. Determine which backport approach to use:
103103
@@ -108,7 +108,7 @@ def get_instructions() -> str:
108108
- <UPSTREAM_REPO>: A temporary upstream repository clone (created in step 3c with -upstream suffix)
109109
110110
When to use this workflow:
111-
- <UPSTREAM_FIX> is a commit or pull request URL
111+
- <UPSTREAM_PATCHES> is a list of commit or pull request URLs
112112
- This includes URLs with .patch suffix (e.g., https://github.com/.../commit/abc123.patch)
113113
- If URL extraction fails, fall back to approach B
114114
@@ -218,22 +218,26 @@ def get_instructions() -> str:
218218
219219
B. GIT AM WORKFLOW (Fallback approach):
220220
221-
Note: For this workflow, use the pre-downloaded patch file at {{local_clone}}/{{jira_issue}}.patch
221+
Note: For this workflow, use the pre-downloaded patch files in the current working directory.
222+
They are called `<JIRA_ISSUE>-<N>.patch` where <N> is a 0-based index. For example,
223+
for a `RHEL-12345` Jira issue the first patch would be called `RHEL-12345-0.patch`.
222224
223-
3a. Backport the patch:
224-
- Use the `git_patch_apply` tool with the patch file: {{local_clone}}/{{jira_issue}}.patch
225+
Backport all patches individually using the steps 3a and 3b below.
226+
227+
3a. Backport one patch at a time using the following steps:
228+
- Use the `git_patch_apply` tool with the patch file: <JIRA_ISSUE>-<N>.patch
225229
- Resolve all conflicts and leave the repository in a dirty state. Delete all *.rej files.
226230
- Use the `git_apply_finish` tool to finish the patch application.
227231
228232
3b. Once there are no more conflicts, use the `git_patch_create` tool with the patch file path
229-
{{local_clone}}/{{jira_issue}}.patch to update the patch file.
233+
<JIRA_ISSUE>-<N>.patch to update the patch file.
230234
231-
4. Update the spec file. Add a new `Patch` tag pointing to the <UPSTREAM_FIX> patch file.
235+
4. Update the spec file. Add a new `Patch` tag for every patch in <UPSTREAM_PATCHES>.
232236
Add the new `Patch` tag after all existing `Patch` tags and, if `Patch` tags are numbered,
233237
make sure it has the highest number. Make sure the patch is applied in the "%prep" section
234238
and the `-p` argument is correct. Add an upstream URL as a comment above
235239
the `Patch:` tag - this URL references the related upstream commit or a pull/merge request.
236-
Default to <UPSTREAM_FIX> if it is an URL.
240+
Include every patch defined in <UPSTREAM_PATCHES> list.
237241
IMPORTANT: Only ADD new patches. Do NOT modify existing Patch tags or their order.
238242
239243
5. Run `centpkg --name=<PACKAGE> --namespace=rpms --release=<DIST_GIT_BRANCH> prep` to see if the new patch
@@ -269,7 +273,10 @@ def get_prompt() -> str:
269273
{{dist_git_branch}} dist-git branch has been checked out. You are working on Jira issue {{jira_issue}}
270274
{{#cve_id}}(a.k.a. {{.}}){{/cve_id}}.
271275
{{^build_error}}
272-
Backport upstream fix {{upstream_fix}}.
276+
Backport upstream patches:
277+
{{#upstream_patches}}
278+
- {{.}}
279+
{{/upstream_patches}}
273280
Unpacked upstream sources are in {{unpacked_sources}}.
274281
{{/build_error}}
275282
{{#build_error}}
@@ -289,7 +296,12 @@ def get_fix_build_error_prompt() -> str:
289296
{{dist_git_branch}} dist-git branch has been checked out. You are working on Jira issue {{jira_issue}}
290297
{{#cve_id}}(a.k.a. {{.}}){{/cve_id}}.
291298
292-
The backport of {{upstream_fix}} was initially successful using the cherry-pick workflow,
299+
Upstream patches that were backported:
300+
{{#upstream_patches}}
301+
- {{.}}
302+
{{/upstream_patches}}
303+
304+
The backport of upstream patches was initially successful using the cherry-pick workflow,
293305
but the build failed with the following error:
294306
295307
{{build_error}}
@@ -492,7 +504,7 @@ async def main() -> None:
492504
local_tool_options = {"working_directory": None}
493505

494506
class State(PackageUpdateState):
495-
upstream_fix: str
507+
upstream_patches: list[str]
496508
cve_id: str | None
497509
unpacked_sources: Path | None = Field(default=None)
498510
backport_log: list[str] = Field(default=[])
@@ -502,7 +514,7 @@ class State(PackageUpdateState):
502514
incremental_fix_attempts: int = Field(default=0) # Track how many times we tried incremental fix
503515

504516
async def run_workflow(
505-
package, dist_git_branch, upstream_fix, jira_issue, cve_id, redis_conn=None
517+
package, dist_git_branch, upstream_patches, jira_issue, cve_id, redis_conn=None
506518
):
507519
local_tool_options["working_directory"] = None
508520

@@ -545,11 +557,14 @@ async def fork_and_prepare_dist_git(state):
545557
state.unpacked_sources = get_unpacked_sources(state.local_clone, state.package)
546558
timeout = aiohttp.ClientTimeout(total=30)
547559
async with aiohttp.ClientSession(timeout=timeout) as session:
548-
async with session.get(state.upstream_fix) as response:
549-
if response.status < 400:
550-
(state.local_clone / f"{state.jira_issue}.patch").write_text(await response.text())
551-
else:
552-
raise ValueError(f"Failed to fetch upstream fix: {response.status}")
560+
for idx, upstream_patch in enumerate(state.upstream_patches):
561+
# should we guess the patch name with log agent?
562+
patch_name = f"{state.jira_issue}-{idx}.patch"
563+
async with session.get(upstream_patch) as response:
564+
if response.status < 400:
565+
(state.local_clone / patch_name).write_text(await response.text())
566+
else:
567+
raise ValueError(f"Failed to fetch upstream patch: {response.status}")
553568
return "run_backport_agent"
554569

555570
async def run_backport_agent(state):
@@ -563,7 +578,7 @@ async def run_backport_agent(state):
563578
dist_git_branch=state.dist_git_branch,
564579
jira_issue=state.jira_issue,
565580
cve_id=state.cve_id,
566-
upstream_fix=state.upstream_fix,
581+
upstream_patches=state.upstream_patches,
567582
build_error=state.build_error,
568583
),
569584
),
@@ -624,7 +639,7 @@ async def fix_build_error(state):
624639
dist_git_branch=state.dist_git_branch,
625640
jira_issue=state.jira_issue,
626641
cve_id=state.cve_id,
627-
upstream_fix=state.upstream_fix,
642+
upstream_patches=state.upstream_patches,
628643
build_error=state.build_error,
629644
),
630645
),
@@ -774,21 +789,22 @@ async def run_log_agent(state):
774789
log_output=log_output,
775790
operation_type="backport",
776791
package=state.package,
777-
details=state.upstream_fix,
792+
details=str(state.upstream_patches),
778793
)
779794
state.log_result = log_output
780795

781796
return "stage_changes"
782797

783798
async def commit_push_and_open_mr(state):
784799
try:
800+
formatted_patches = "\n".join(f" - {p}" for p in state.upstream_patches)
785801
state.merge_request_url = await tasks.commit_push_and_open_mr(
786802
local_clone=state.local_clone,
787803
commit_message=(
788804
f"{state.log_result.title}\n\n"
789805
f"{state.log_result.description}\n\n"
790806
+ (f"CVE: {state.cve_id}\n" if state.cve_id else "")
791-
+ f"Upstream fix: {state.upstream_fix}\n"
807+
+ "Upstream patches:\n" + formatted_patches + "\n"
792808
+ f"Resolves: {state.jira_issue}\n\n"
793809
f"This commit was backported {I_AM_JOTNAR}\n\n"
794810
"Assisted-by: Jotnar\n"
@@ -801,7 +817,7 @@ async def commit_push_and_open_mr(state):
801817
f"This merge request was created {I_AM_JOTNAR}\n"
802818
f"{CAREFULLY_REVIEW_CHANGES}\n\n"
803819
f"{state.log_result.description}\n\n"
804-
f"Upstream patch: {state.upstream_fix}\n\n"
820+
+ "Upstream patches:\n" + formatted_patches + "\n"
805821
f"Resolves: {state.jira_issue}\n\n"
806822
f"Backporting steps:\n\n{state.backport_log[-1]}"
807823
),
@@ -849,7 +865,7 @@ async def comment_in_jira(state):
849865
State(
850866
package=package,
851867
dist_git_branch=dist_git_branch,
852-
upstream_fix=upstream_fix,
868+
upstream_patches=upstream_patches,
853869
jira_issue=jira_issue,
854870
cve_id=cve_id,
855871
),
@@ -859,14 +875,15 @@ async def comment_in_jira(state):
859875
if (
860876
(package := os.getenv("PACKAGE", None))
861877
and (branch := os.getenv("BRANCH", None))
862-
and (upstream_fix := os.getenv("UPSTREAM_FIX", None))
878+
and (upstream_patches_raw := os.getenv("UPSTREAM_PATCHES", None))
863879
and (jira_issue := os.getenv("JIRA_ISSUE", None))
864880
):
881+
upstream_patches = upstream_patches_raw.split(",")
865882
logger.info("Running in direct mode with environment variables")
866883
state = await run_workflow(
867884
package=package,
868885
dist_git_branch=branch,
869-
upstream_fix=upstream_fix,
886+
upstream_patches=upstream_patches,
870887
jira_issue=jira_issue,
871888
cve_id=os.getenv("CVE_ID", None),
872889
redis_conn=None,
@@ -928,7 +945,7 @@ async def retry(task, error):
928945
state = await run_workflow(
929946
package=backport_data.package,
930947
dist_git_branch=dist_git_branch,
931-
upstream_fix=backport_data.patch_url,
948+
upstream_patches=backport_data.patch_urls,
932949
jira_issue=backport_data.jira_issue,
933950
cve_id=backport_data.cve_id,
934951
redis_conn=redis,

common/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ class BackportInputSchema(BaseModel):
9595
dist_git_branch: str = Field(description="Git branch in dist-git to be updated")
9696
jira_issue: str = Field(description="Jira issue to reference as resolved")
9797
cve_id: str | None = Field(default=None, description="CVE ID if the jira issue is a CVE")
98-
upstream_fix: str = Field(description="URL to the upstream fix (commit URL or patch URL)")
98+
upstream_patches: list[str] = Field(
99+
description="List of URLs to upstream patches that were validated using the PatchValidator tool")
99100
build_error: str | None = Field(description="Error encountered during package build")
100101

101102

@@ -273,4 +274,4 @@ class CachedMRMetadata(BaseModel):
273274
operation_type: str = Field(description="Type of operation (backport or rebase)")
274275
title: str = Field(description="Merge request title")
275276
package: str = Field(description="Package name")
276-
details: str = Field(description="Operation-specific identifier (upstream_fix URL for backport, version for rebase)")
277+
details: str = Field(description="Operation-specific identifier (list of upstream patch URLs for backport, version for rebase)")

0 commit comments

Comments
 (0)