@@ -61,6 +61,42 @@ impl CliId {
61
61
}
62
62
}
63
63
64
+ fn find_branches_by_name ( ctx : & CommandContext , name : & str ) -> anyhow:: Result < Vec < Self > > {
65
+ let stacks = crate :: log:: stacks ( ctx) ?;
66
+ let mut matches = Vec :: new ( ) ;
67
+
68
+ for stack in stacks {
69
+ for head in & stack. heads {
70
+ let branch_name = head. name . to_string ( ) ;
71
+ // Exact match or partial match
72
+ if branch_name == name || branch_name. contains ( name) {
73
+ matches. push ( CliId :: branch ( & branch_name) ) ;
74
+ }
75
+ }
76
+ }
77
+
78
+ Ok ( matches)
79
+ }
80
+
81
+ fn find_commits_by_sha ( ctx : & CommandContext , sha_prefix : & str ) -> anyhow:: Result < Vec < Self > > {
82
+ let mut matches = Vec :: new ( ) ;
83
+
84
+ // Only try SHA matching if the input looks like a hex string
85
+ if sha_prefix. chars ( ) . all ( |c| c. is_ascii_hexdigit ( ) ) && sha_prefix. len ( ) >= 4 {
86
+ let all_commits = crate :: log:: all_commits ( ctx) ?;
87
+ for commit_id in all_commits {
88
+ if let CliId :: Commit { oid } = & commit_id {
89
+ let sha_string = oid. to_string ( ) ;
90
+ if sha_string. starts_with ( sha_prefix) {
91
+ matches. push ( commit_id) ;
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ Ok ( matches)
98
+ }
99
+
64
100
pub fn matches ( & self , s : & str ) -> bool {
65
101
s == self . to_string ( )
66
102
}
@@ -77,71 +113,79 @@ impl CliId {
77
113
78
114
pub fn from_str ( ctx : & mut CommandContext , s : & str ) -> anyhow:: Result < Vec < Self > > {
79
115
if s. len ( ) < 2 {
80
- return Err ( anyhow:: anyhow!( "Id needs to be 3 characters long: {}" , s) ) ;
116
+ return Err ( anyhow:: anyhow!( "Id needs to be at least 2 characters long: {}" , s) ) ;
81
117
}
82
118
83
- // First try with the full input string for prefix matching
119
+ let mut matches = Vec :: new ( ) ;
120
+
121
+ // First, try exact branch name match
122
+ if let Ok ( branch_matches) = Self :: find_branches_by_name ( ctx, s) {
123
+ matches. extend ( branch_matches) ;
124
+ }
125
+
126
+ // Then try partial SHA matches (for commits)
127
+ if let Ok ( commit_matches) = Self :: find_commits_by_sha ( ctx, s) {
128
+ matches. extend ( commit_matches) ;
129
+ }
130
+
131
+ // Then try CliId matching (both prefix and exact)
84
132
if s. len ( ) > 2 {
85
- let mut everything = Vec :: new ( ) ;
133
+ // For longer strings, try prefix matching on CliIds
134
+ let mut cli_matches = Vec :: new ( ) ;
86
135
crate :: status:: all_files ( ctx) ?
87
136
. into_iter ( )
88
137
. filter ( |id| id. matches_prefix ( s) )
89
- . for_each ( |id| everything . push ( id) ) ;
138
+ . for_each ( |id| cli_matches . push ( id) ) ;
90
139
crate :: status:: all_committed_files ( ctx) ?
91
140
. into_iter ( )
92
141
. filter ( |id| id. matches_prefix ( s) )
93
- . for_each ( |id| everything . push ( id) ) ;
142
+ . for_each ( |id| cli_matches . push ( id) ) ;
94
143
crate :: status:: all_branches ( ctx) ?
95
144
. into_iter ( )
96
145
. filter ( |id| id. matches_prefix ( s) )
97
- . for_each ( |id| everything . push ( id) ) ;
146
+ . for_each ( |id| cli_matches . push ( id) ) ;
98
147
crate :: log:: all_commits ( ctx) ?
99
148
. into_iter ( )
100
149
. filter ( |id| id. matches_prefix ( s) )
101
- . for_each ( |id| everything . push ( id) ) ;
150
+ . for_each ( |id| cli_matches . push ( id) ) ;
102
151
if CliId :: unassigned ( ) . matches_prefix ( s) {
103
- everything . push ( CliId :: unassigned ( ) ) ;
152
+ cli_matches . push ( CliId :: unassigned ( ) ) ;
104
153
}
105
-
106
- // If we found exactly one match with the full prefix, return it
107
- if everything. len ( ) == 1 {
108
- return Ok ( everything) ;
109
- }
110
- // If we found multiple matches with the full prefix, return them all (ambiguous)
111
- if everything. len ( ) > 1 {
112
- return Ok ( everything) ;
154
+ matches. extend ( cli_matches) ;
155
+ } else {
156
+ // For 2-character strings, try exact CliId matching
157
+ let mut cli_matches = Vec :: new ( ) ;
158
+ crate :: status:: all_files ( ctx) ?
159
+ . into_iter ( )
160
+ . filter ( |id| id. matches ( s) )
161
+ . for_each ( |id| cli_matches. push ( id) ) ;
162
+ crate :: status:: all_committed_files ( ctx) ?
163
+ . into_iter ( )
164
+ . filter ( |id| id. matches ( s) )
165
+ . for_each ( |id| cli_matches. push ( id) ) ;
166
+ crate :: status:: all_branches ( ctx) ?
167
+ . into_iter ( )
168
+ . filter ( |id| id. matches ( s) )
169
+ . for_each ( |id| cli_matches. push ( id) ) ;
170
+ crate :: log:: all_commits ( ctx) ?
171
+ . into_iter ( )
172
+ . filter ( |id| id. matches ( s) )
173
+ . for_each ( |id| cli_matches. push ( id) ) ;
174
+ if CliId :: unassigned ( ) . matches ( s) {
175
+ cli_matches. push ( CliId :: unassigned ( ) ) ;
113
176
}
114
- // If no matches with full prefix, fall through to 2-char matching
177
+ matches. extend ( cli_matches ) ;
115
178
}
116
179
117
- // Fall back to original 2-character matching behavior
118
- let s = & s[ ..2 ] ;
119
- let mut everything = Vec :: new ( ) ;
120
- crate :: status:: all_files ( ctx) ?
121
- . into_iter ( )
122
- . filter ( |id| id. matches ( s) )
123
- . for_each ( |id| everything. push ( id) ) ;
124
- crate :: status:: all_committed_files ( ctx) ?
125
- . into_iter ( )
126
- . filter ( |id| id. matches ( s) )
127
- . for_each ( |id| everything. push ( id) ) ;
128
- crate :: status:: all_branches ( ctx) ?
129
- . into_iter ( )
130
- . filter ( |id| id. matches ( s) )
131
- . for_each ( |id| everything. push ( id) ) ;
132
- crate :: log:: all_commits ( ctx) ?
133
- . into_iter ( )
134
- . filter ( |id| id. matches ( s) )
135
- . for_each ( |id| everything. push ( id) ) ;
136
- everything. push ( CliId :: unassigned ( ) ) ;
137
-
138
- let mut matches = Vec :: new ( ) ;
139
- for id in everything {
140
- if id. matches ( s) {
141
- matches. push ( id) ;
180
+ // Remove duplicates while preserving order
181
+ let mut unique_matches = Vec :: new ( ) ;
182
+ for m in matches {
183
+ if !unique_matches. contains ( & m) {
184
+ unique_matches. push ( m) ;
142
185
}
143
186
}
144
- Ok ( matches)
187
+
188
+ Ok ( unique_matches)
145
189
}
146
190
}
147
191
0 commit comments