Fix gh-316: Prefer exact ID matches over prefix matches
The ID disambiguation logic treated 'offlinebrew-3d0' as ambiguous when child IDs like 'offlinebrew-3d0.1' existed. Now the system: 1. Checks for exact full ID matches first (issue.ID == input) 2. Checks for exact hash matches (handling cross-prefix scenarios) 3. Only falls back to substring matching if no exact match is found Added test cases verifying: - 'offlinebrew-3d0' matches exactly, not ambiguously with children - '3d0' without prefix still resolves to exact match Amp-Thread-ID: https://ampcode.com/threads/T-5358ea57-e9ea-49e9-aedf-7044ebf8b52a Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -81,14 +81,41 @@ func ResolvePartialID(ctx context.Context, store storage.Storage, input string)
|
||||
hashPart := strings.TrimPrefix(normalizedID, prefixWithHyphen)
|
||||
|
||||
var matches []string
|
||||
var exactMatch string
|
||||
|
||||
for _, issue := range issues {
|
||||
issueHash := strings.TrimPrefix(issue.ID, prefixWithHyphen)
|
||||
// Check for exact full ID match first (case: user typed full ID with different prefix)
|
||||
if issue.ID == input {
|
||||
exactMatch = issue.ID
|
||||
break
|
||||
}
|
||||
|
||||
// Extract hash from each issue, regardless of its prefix
|
||||
// This handles cross-prefix matching (e.g., "3d0" matching "offlinebrew-3d0")
|
||||
var issueHash string
|
||||
if idx := strings.Index(issue.ID, "-"); idx >= 0 {
|
||||
issueHash = issue.ID[idx+1:]
|
||||
} else {
|
||||
issueHash = issue.ID
|
||||
}
|
||||
|
||||
// Check for exact hash match (excluding hierarchical children)
|
||||
if issueHash == hashPart {
|
||||
exactMatch = issue.ID
|
||||
// Don't break - keep searching in case there's a full ID match
|
||||
}
|
||||
|
||||
// Check if the issue hash contains the input hash as substring
|
||||
if strings.Contains(issueHash, hashPart) {
|
||||
matches = append(matches, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer exact match over substring matches
|
||||
if exactMatch != "" {
|
||||
return exactMatch, nil
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
return "", fmt.Errorf("no issue found matching %q", input)
|
||||
}
|
||||
|
||||
@@ -90,6 +90,21 @@ func TestResolvePartialID(t *testing.T) {
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
// Test hierarchical IDs - parent and child
|
||||
parentIssue := &types.Issue{
|
||||
ID: "offlinebrew-3d0",
|
||||
Title: "Parent Epic",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeEpic,
|
||||
}
|
||||
childIssue := &types.Issue{
|
||||
ID: "offlinebrew-3d0.1",
|
||||
Title: "Child Task",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
}
|
||||
|
||||
if err := store.CreateIssue(ctx, issue1, "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -100,6 +115,12 @@ func TestResolvePartialID(t *testing.T) {
|
||||
if err := store.CreateIssue(ctx, issue3, "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := store.CreateIssue(ctx, parentIssue, "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := store.CreateIssue(ctx, childIssue, "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Set config for prefix
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "bd-"); err != nil {
|
||||
@@ -149,6 +170,16 @@ func TestResolvePartialID(t *testing.T) {
|
||||
input: "bd-1",
|
||||
expected: "bd-1", // Will match exactly, not ambiguously
|
||||
},
|
||||
{
|
||||
name: "exact match parent ID with hierarchical child - gh-316",
|
||||
input: "offlinebrew-3d0",
|
||||
expected: "offlinebrew-3d0", // Should match exactly, not be ambiguous with offlinebrew-3d0.1
|
||||
},
|
||||
{
|
||||
name: "exact match parent without prefix - gh-316",
|
||||
input: "3d0",
|
||||
expected: "offlinebrew-3d0", // Should still prefer exact hash match
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user