Skip to content

[ZEPPELIN-6404] Add Go CLI tool for merging pull requests#5183

Open
jongyoul wants to merge 18 commits intoapache:masterfrom
jongyoul:ZEPPELIN-6404-go-merge-cli-v2
Open

[ZEPPELIN-6404] Add Go CLI tool for merging pull requests#5183
jongyoul wants to merge 18 commits intoapache:masterfrom
jongyoul:ZEPPELIN-6404-go-merge-cli-v2

Conversation

@jongyoul
Copy link
Member

Summary

  • Rewrite dev/merge_zeppelin_pr.py as a single-file Go CLI (dev/merge-pr.go)
  • No external dependencies — uses only Go standard library (flag package)
  • Runs directly with go run dev/merge-pr.go --pr <number> [flags] (no go.mod needed)
  • Non-interactive, AI-agent friendly design

Motivation

The existing Python merge script (dev/merge_zeppelin_pr.py) requires a Python virtual environment with dependencies (jira, python-dotenv), and is interactive (prompts for user input). This makes it difficult to use from CI/CD or AI agent workflows.

The Go version:

  • Requires only a Go toolchain (no venv, no pip install)
  • All flags are CLI arguments — no interactive prompts
  • Single file, ~536 lines, zero external dependencies

Usage

# Dry run (shows PR info without merging)
go run dev/merge-pr.go --pr 5167 --dry-run

# Merge and resolve JIRA
go run dev/merge-pr.go --pr 5167 --resolve-jira --fix-versions 0.13.0

# Merge and cherry-pick into release branches
go run dev/merge-pr.go --pr 5167 --release-branches branch-0.12,branch-0.11

Flags

Flag Description
--pr Pull request number (required)
--target Target branch (default: PR base branch)
--fix-versions JIRA fix version(s), comma-separated
--release-branches Release branch(es) to cherry-pick into, comma-separated
--resolve-jira Resolve associated JIRA issue(s)
--dry-run Show what would be done without making changes
--push-remote Git remote for pushing (default: apache)
--github-token GitHub OAuth token (env: GITHUB_OAUTH_KEY)
--jira-token JIRA access token (env: JIRA_ACCESS_TOKEN)

Test Plan

  • go run dev/merge-pr.go --help — shows usage
  • go run dev/merge-pr.go --pr 5167 --dry-run — fetches PR info correctly
  • Full merge test with a real PR
  • Cherry-pick into release branch test
  • JIRA resolution test

jongyoul and others added 11 commits March 16, 2026 23:59
… library

Replace multi-file cobra-based CLI with a single dev/merge-pr.go using
only Go standard library (flag package). No go.mod needed - runs directly
with `go run dev/merge-pr.go --pr <number> [flags]`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… issues

- Extract 6 regexp patterns to package-level vars (avoid recompilation)
- Add shortSHA helper to unify hash truncation with length guard
- Remove unused flagPRRemote flag
- Use local variable for target branch instead of mutating global
- Deduplicate cherry-pick cleanup with closure
- Fix jiraTransitions error handling for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fix-version inference

- standardizeTitle now extracts [COMPONENT] brackets (e.g. [SPARK], [FLINK])
  and assembles them after JIRA refs, matching Python's standardize_jira_ref
- Add inferFixVersions to auto-map release branches to JIRA versions
  (e.g. branch-0.12 → smallest 0.12.x unreleased version)
- Remove redundant X.Y.0 when X.(Y-1).0 is also present

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ase branch versions

- --fix-versions and release branch inference now work together
- e.g. --fix-versions 0.13.0 --release-branches branch-0.12 → 0.13.0, 0.12.1
- Master auto-inference only runs when --fix-versions is not specified

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ept header

- dry-run now shows the effective command with all inferred values resolved
- Fix HTTP Accept header: use application/json instead of GitHub-specific
  header that caused JIRA API 406 errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allows merging PRs with natural language input:
  /merge-pr 5167 fix-versions 0.13.0, cherry-pick into branch-0.12

Always dry-runs first and asks for confirmation before actual merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The skill now checks for Go availability and downloads it to .go/
directory if not found, so the merge tool works without pre-installed Go.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After dry-run, ask user if effective command looks correct and allow
adjusting fix-versions, release-branches before actual merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Posts a comment on the GitHub PR listing which branches were merged into,
e.g. "Merged into master (394e245). Cherry-picked into branch-0.12."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jongyoul jongyoul marked this pull request as ready for review March 17, 2026 05:29
Copilot AI review requested due to automatic review settings March 17, 2026 05:29
@jongyoul
Copy link
Member Author

@pan3793 @Reamer @tbonelee @ParkGyeongTae I just want to know your opinion about this kind of way. I added CLI application and Claude command for merging PRs. I believe that we already use AI, and which reduces our efforts for regular jobs like this. Moreover, it's the starting point for the discussion to adopt AI into the project so feel free to leave your opinion. You can use email or slack for discussion as well. 🙏

Ref.
The reason why I re-write the application. We already have python script but it needs to install dependencies or virtual environment. It works well in our terminal but AI sometimes miss to install or use it when executing it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new single-file Go-based CLI in dev/ to automate merging Apache Zeppelin GitHub pull requests (optionally cherry-picking to release branches and resolving JIRA), targeting non-interactive / CI-friendly usage.

Changes:

  • Introduce dev/merge-pr.go Go CLI that talks to GitHub + JIRA APIs and runs local git commands for cherry-picks.
  • Add .claude/commands/merge-pr.md instructions for invoking the tool (including an optional local Go bootstrap flow).
  • Update .gitignore to exclude a local .go/ Go toolchain directory.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.

File Description
dev/merge-pr.go New Go CLI for merging PRs, cherry-picking to release branches, and resolving JIRA issues.
.gitignore Ignores .go/ directory intended for a locally downloaded Go toolchain.
.claude/commands/merge-pr.md Adds an AI-agent oriented command doc for running the merge CLI (dry-run first, then merge).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

dev/merge-pr.go Outdated
Comment on lines +148 to +153
type pullRequest struct {
URL string `json:"url"`
Title string `json:"title"`
Body string `json:"body"`
Mergeable bool `json:"mergeable"`
Base struct{ Ref string `json:"ref"` } `json:"base"`
dev/merge-pr.go Outdated
Comment on lines +118 to +143
func httpDo(method, url string, body interface{}, auth string) ([]byte, int, error) {
var r io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, 0, err
}
r = bytes.NewReader(b)
}
req, err := http.NewRequest(method, url, r)
if err != nil {
return nil, 0, err
}
if auth != "" {
req.Header.Set("Authorization", auth)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
return data, resp.StatusCode, err
dev/merge-pr.go Outdated
}
fmt.Printf("\nPR #%d merged! (hash: %s)\n", flagPR, shortSHA(resp.SHA))

gitRun("fetch", flagPushRemote, target)

1. Check if `go` is available by running `go version`. If not found:
- Detect OS and arch (`uname -s`, `uname -m`)
- Download Go to `.go/` directory: `curl -fsSL https://go.dev/dl/go1.23.6.<os>-<arch>.tar.gz | tar -xz -C .go --strip-components=1`
Comment on lines +18 to +19
- Detect OS and arch (`uname -s`, `uname -m`)
- Download Go to `.go/` directory: `curl -fsSL https://go.dev/dl/go1.23.6.<os>-<arch>.tar.gz | tar -xz -C .go --strip-components=1`
dev/merge-pr.go Outdated
fmt.Printf("title: %s\n", title)
fmt.Printf("source: %s\n", src)
fmt.Printf("target: %s\n", target)
fmt.Printf("url: %s\n", pr.URL)
@pan3793
Copy link
Member

pan3793 commented Mar 17, 2026

Go toolchain installation is another burden ...

JEP 330 (Java 11) supports run Java source file directly, if we don't want to introduce additional toolchains, maybe we can write it in Java

@jongyoul
Copy link
Member Author

@pan3793 Yes, that can also be possible. By the way, how about adding some AI related instructions into the repository?

@tbonelee
Copy link
Contributor

That’s a fair point about Go toolchain installation being an extra burden. Still, avoiding that does not necessarily remove all setup tradeoffs. If we want to keep conveniences like .env parsing, a small external dependency may still be worth it regardless of the language.
Since the repo does not seem heavily invested in Python-based dev scripts today, Go still seems like a reasonable option to me, especially because the required version can be declared in go.mod, while Python workflows often rely a bit more on separately managing the interpreter and environment.

@jongyoul
Copy link
Member Author

@tbonelee Yes, your point is also good. But we can try to re-write it using java - which is a mandatory for the project. It wasn't unavoidable, but I didn't want to cause confusion to AI.

jongyoul and others added 4 commits March 17, 2026 15:39
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single-file Java 11 CLI (java dev/merge-pr.java) that mirrors the Go
version. No external dependencies - uses java.net.http.HttpClient and
manual JSON parsing. Supports GitHub squash merge, cherry-pick into
release branches, JIRA resolution, fix-version inference, PR commenting,
and effective command display in dry-run mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Delete dev/merge-pr.go
- Remove .go/ from .gitignore
- Update merge-pr.md skill to reference java dev/merge-pr.java

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert all static fields/methods to instance-based: constructor
parses args into instance fields, run() and helpers are instance
methods. Only constants, JSON/utility helpers, and main() remain
static. Also extract resolveFixVersionNames() and
commentMergeSummary() for readability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pan3793
Copy link
Member

pan3793 commented Mar 17, 2026

@jongyoul, for the AI part, I saw some ASF projects start to add AGENTS.md to the code repo, and exclude folders like .clause, .github/skills, etc. from the git, there are some guidelines and rules about AI in the ASF policy that I haven't read carefully yet.

BTW, the Apache Iceberg dev mailing list is discussing the AI policy seriously, this should be a good reference for us.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
return http.send(builder.build(), HttpResponse.BodyHandlers.ofString());
}

// ── Simple JSON parser (no external deps) ──────────────────────────────
Copy link
Member

@pan3793 pan3793 Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a pity that Java does not have built-in JSON support. I re-read your motivation, it seems the concerns are about venv and 3rd deps installation, can we modify the Python script to remove that? e.g.,

  • replace jira.client usage with HTTP call
  • process .env only when python-dotenv is installed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes, it looks reasonable. My point were actually two parts.

  • 3rd party dependencies as you mentioned
  • Interactive wasn't helpful to AI

Moreover, concerning about JSON, I totally agree with you. Python is a better choice.

@jongyoul
Copy link
Member Author

@jongyoul, for the AI part, I saw some ASF projects start to add AGENTS.md to the code repo, and exclude folders like .clause, .github/skills, etc. from the git, there are some guidelines and rules about AI in the ASF policy that I haven't read carefully yet.

BTW, the Apache Iceberg dev mailing list is discussing the AI policy seriously, this should be a good reference for us.

@pan3793 I read the generative-tooling part on the ASF legal, and there's no limitation but I also don't think it's good to stick on some specific AI. Let me remove .claude including commands, and I will try to write some draft AGENTS.md in another PR.

jongyoul and others added 2 commits March 17, 2026 17:16
Replace dev/merge-pr.java with dev/merge_pr.py using only Python 3
built-in libraries (urllib, json, subprocess, argparse, re). Remove
.claude/commands/merge-pr.md from git tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… constants

- Resolve fix versions once and pass to both dry-run and JIRA resolution
- Unify _resolve_fix_version_names and _do_resolve_jira fix-version logic
  into single _resolve_fix_versions method
- Extract constants: DEFAULT_BRANCH, DEFAULT_REMOTE, JIRA_RESOLVE_TRANSITION,
  JIRA_CLOSED_STATUSES
- Use try/finally in cherry-pick loop to guarantee HEAD restoration
- Extract _pick_branch_name helper
- Rename _http param body→payload to avoid shadowing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants