feat(mq): auto-target integration branch for epic children
When 'gt mq submit' is called: 1. Parse source issue from branch 2. Check if issue has a parent epic 3. Check if integration/<epic> branch exists 4. If yes: set target=integration/<epic> 5. If no: set target=main The --epic flag still works for explicit targeting. Closes gt-h5n.7 🤝 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,13 +72,20 @@ Auto-detection:
|
|||||||
- Issue: parsed from branch name (e.g., polecat/Nux/gt-xyz → gt-xyz)
|
- Issue: parsed from branch name (e.g., polecat/Nux/gt-xyz → gt-xyz)
|
||||||
- Worker: parsed from branch name
|
- Worker: parsed from branch name
|
||||||
- Rig: detected from current directory
|
- Rig: detected from current directory
|
||||||
- Target: main (or integration/<epic> if --epic specified)
|
- Target: automatically determined (see below)
|
||||||
- Priority: inherited from source issue
|
- Priority: inherited from source issue
|
||||||
|
|
||||||
|
Target branch auto-detection:
|
||||||
|
1. If --epic is specified: target integration/<epic>
|
||||||
|
2. If source issue has a parent epic with integration/<epic> branch: target it
|
||||||
|
3. Otherwise: target main
|
||||||
|
|
||||||
|
This ensures batch work on epics automatically flows to integration branches.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
gt mq submit # Auto-detect everything
|
gt mq submit # Auto-detect everything
|
||||||
gt mq submit --issue gt-abc # Explicit issue
|
gt mq submit --issue gt-abc # Explicit issue
|
||||||
gt mq submit --epic gt-xyz # Target integration branch
|
gt mq submit --epic gt-xyz # Target integration branch explicitly
|
||||||
gt mq submit --priority 0 # Override priority (P0)`,
|
gt mq submit --priority 0 # Override priority (P0)`,
|
||||||
RunE: runMqSubmit,
|
RunE: runMqSubmit,
|
||||||
}
|
}
|
||||||
@@ -382,15 +389,25 @@ func runMqSubmit(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("cannot determine source issue from branch '%s'; use --issue to specify", branch)
|
return fmt.Errorf("cannot determine source issue from branch '%s'; use --issue to specify", branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize beads for looking up source issue
|
||||||
|
bd := beads.New(cwd)
|
||||||
|
|
||||||
// Determine target branch
|
// Determine target branch
|
||||||
target := "main"
|
target := "main"
|
||||||
if mqSubmitEpic != "" {
|
if mqSubmitEpic != "" {
|
||||||
|
// Explicit --epic flag takes precedence
|
||||||
target = "integration/" + mqSubmitEpic
|
target = "integration/" + mqSubmitEpic
|
||||||
|
} else {
|
||||||
|
// Auto-detect: check if source issue has a parent epic with an integration branch
|
||||||
|
autoTarget, err := detectIntegrationBranch(bd, g, issueID)
|
||||||
|
if err != nil {
|
||||||
|
// Non-fatal: log and continue with main as target
|
||||||
|
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf("(note: %v)", err)))
|
||||||
|
} else if autoTarget != "" {
|
||||||
|
target = autoTarget
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize beads
|
|
||||||
bd := beads.New(cwd)
|
|
||||||
|
|
||||||
// Get source issue for priority inheritance
|
// Get source issue for priority inheritance
|
||||||
var priority int
|
var priority int
|
||||||
if mqSubmitPriority >= 0 {
|
if mqSubmitPriority >= 0 {
|
||||||
@@ -1469,3 +1486,53 @@ func resetHard(g *git.Git, ref string) error {
|
|||||||
cmd.Dir = g.WorkDir()
|
cmd.Dir = g.WorkDir()
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectIntegrationBranch checks if an issue is a child of an epic that has an integration branch.
|
||||||
|
// Returns the integration branch target (e.g., "integration/gt-epic") if found, or "" if not.
|
||||||
|
func detectIntegrationBranch(bd *beads.Beads, g *git.Git, issueID string) (string, error) {
|
||||||
|
// Get the source issue
|
||||||
|
issue, err := bd.Show(issueID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("looking up issue %s: %w", issueID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if issue has a parent
|
||||||
|
if issue.Parent == "" {
|
||||||
|
return "", nil // No parent, no integration branch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parent issue
|
||||||
|
parent, err := bd.Show(issue.Parent)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("looking up parent %s: %w", issue.Parent, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if parent is an epic
|
||||||
|
if parent.Type != "epic" {
|
||||||
|
return "", nil // Parent is not an epic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if integration branch exists
|
||||||
|
integrationBranch := "integration/" + parent.ID
|
||||||
|
|
||||||
|
// Check local first (faster)
|
||||||
|
exists, err := g.BranchExists(integrationBranch)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("checking local branch: %w", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return integrationBranch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check remote
|
||||||
|
exists, err = g.RemoteBranchExists("origin", integrationBranch)
|
||||||
|
if err != nil {
|
||||||
|
// Remote check failure is non-fatal
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return integrationBranch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil // No integration branch found
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user