@@ -61,37 +61,134 @@ impl CliId {
6161 }
6262 }
6363
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+
64100 pub fn matches ( & self , s : & str ) -> bool {
65101 s == self . to_string ( )
66102 }
67103
104+ pub fn matches_prefix ( & self , s : & str ) -> bool {
105+ match self {
106+ CliId :: Commit { oid } => {
107+ let oid_hash = hash ( & oid. to_string ( ) ) ;
108+ oid_hash. starts_with ( s)
109+ }
110+ _ => self . to_string ( ) . starts_with ( s) ,
111+ }
112+ }
113+
68114 pub fn from_str ( ctx : & mut CommandContext , s : & str ) -> anyhow:: Result < Vec < Self > > {
69115 if s. len ( ) < 2 {
70- return Err ( anyhow:: anyhow!( "Id needs to be 3 characters long: {}" , s) ) ;
116+ return Err ( anyhow:: anyhow!(
117+ "Id needs to be at least 2 characters long: {}" ,
118+ s
119+ ) ) ;
71120 }
72- let s = & s[ ..2 ] ;
73- let mut everything = Vec :: new ( ) ;
74- crate :: status:: all_files ( ctx) ?
75- . into_iter ( )
76- . filter ( |id| id. matches ( s) )
77- . for_each ( |id| everything. push ( id) ) ;
78- crate :: status:: all_branches ( ctx) ?
79- . into_iter ( )
80- . filter ( |id| id. matches ( s) )
81- . for_each ( |id| everything. push ( id) ) ;
82- crate :: log:: all_commits ( ctx) ?
83- . into_iter ( )
84- . filter ( |id| id. matches ( s) )
85- . for_each ( |id| everything. push ( id) ) ;
86- everything. push ( CliId :: unassigned ( ) ) ;
87121
88122 let mut matches = Vec :: new ( ) ;
89- for id in everything {
90- if id. matches ( s) {
91- matches. push ( id) ;
123+
124+ // First, try exact branch name match
125+ if let Ok ( branch_matches) = Self :: find_branches_by_name ( ctx, s) {
126+ matches. extend ( branch_matches) ;
127+ }
128+
129+ // Then try partial SHA matches (for commits)
130+ if let Ok ( commit_matches) = Self :: find_commits_by_sha ( ctx, s) {
131+ matches. extend ( commit_matches) ;
132+ }
133+
134+ // Then try CliId matching (both prefix and exact)
135+ if s. len ( ) > 2 {
136+ // For longer strings, try prefix matching on CliIds
137+ let mut cli_matches = Vec :: new ( ) ;
138+ crate :: status:: all_files ( ctx) ?
139+ . into_iter ( )
140+ . filter ( |id| id. matches_prefix ( s) )
141+ . for_each ( |id| cli_matches. push ( id) ) ;
142+ crate :: status:: all_committed_files ( ctx) ?
143+ . into_iter ( )
144+ . filter ( |id| id. matches_prefix ( s) )
145+ . for_each ( |id| cli_matches. push ( id) ) ;
146+ crate :: status:: all_branches ( ctx) ?
147+ . into_iter ( )
148+ . filter ( |id| id. matches_prefix ( s) )
149+ . for_each ( |id| cli_matches. push ( id) ) ;
150+ crate :: log:: all_commits ( ctx) ?
151+ . into_iter ( )
152+ . filter ( |id| id. matches_prefix ( s) )
153+ . for_each ( |id| cli_matches. push ( id) ) ;
154+ if CliId :: unassigned ( ) . matches_prefix ( s) {
155+ cli_matches. push ( CliId :: unassigned ( ) ) ;
156+ }
157+ matches. extend ( cli_matches) ;
158+ } else {
159+ // For 2-character strings, try exact CliId matching
160+ let mut cli_matches = Vec :: new ( ) ;
161+ crate :: status:: all_files ( ctx) ?
162+ . into_iter ( )
163+ . filter ( |id| id. matches ( s) )
164+ . for_each ( |id| cli_matches. push ( id) ) ;
165+ crate :: status:: all_committed_files ( ctx) ?
166+ . into_iter ( )
167+ . filter ( |id| id. matches ( s) )
168+ . for_each ( |id| cli_matches. push ( id) ) ;
169+ crate :: status:: all_branches ( ctx) ?
170+ . into_iter ( )
171+ . filter ( |id| id. matches ( s) )
172+ . for_each ( |id| cli_matches. push ( id) ) ;
173+ crate :: log:: all_commits ( ctx) ?
174+ . into_iter ( )
175+ . filter ( |id| id. matches ( s) )
176+ . for_each ( |id| cli_matches. push ( id) ) ;
177+ if CliId :: unassigned ( ) . matches ( s) {
178+ cli_matches. push ( CliId :: unassigned ( ) ) ;
92179 }
180+ matches. extend ( cli_matches) ;
93181 }
94- Ok ( matches)
182+
183+ // Remove duplicates while preserving order
184+ let mut unique_matches = Vec :: new ( ) ;
185+ for m in matches {
186+ if !unique_matches. contains ( & m) {
187+ unique_matches. push ( m) ;
188+ }
189+ }
190+
191+ Ok ( unique_matches)
95192 }
96193}
97194
@@ -117,6 +214,8 @@ impl Display for CliId {
117214 write ! ( f, "00" )
118215 }
119216 CliId :: Commit { oid } => {
217+ // let oid_str = oid.to_string();
218+ // write!(f, "{}", hash(&oid_str))
120219 let oid = oid. to_string ( ) ;
121220 write ! ( f, "{}" , & oid[ ..2 ] )
122221 }
@@ -129,12 +228,15 @@ pub(crate) fn hash(input: &str) -> String {
129228 for byte in input. bytes ( ) {
130229 hash = hash. wrapping_mul ( 31 ) . wrapping_add ( byte as u64 ) ;
131230 }
132- // Convert to base 36 (0-9, a-z)
133- let chars = "0123456789abcdefghijklmnopqrstuvwxyz" ;
134- let mut result = String :: new ( ) ;
135- for _ in 0 ..2 {
136- result. push ( chars. chars ( ) . nth ( ( hash % 36 ) as usize ) . unwrap ( ) ) ;
137- hash /= 36 ;
138- }
139- result
231+
232+ // First character: g-z (20 options)
233+ let first_chars = "ghijklmnopqrstuvwxyz" ;
234+ let first_char = first_chars. chars ( ) . nth ( ( hash % 20 ) as usize ) . unwrap ( ) ;
235+ hash /= 20 ;
236+
237+ // Second character: 0-9,a-z (36 options)
238+ let second_chars = "0123456789abcdefghijklmnopqrstuvwxyz" ;
239+ let second_char = second_chars. chars ( ) . nth ( ( hash % 36 ) as usize ) . unwrap ( ) ;
240+
241+ format ! ( "{first_char}{second_char}" )
140242}
0 commit comments