@@ -111,9 +111,10 @@ static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload
111111 * This is the main "checkout <branch>" function, responsible for performing
112112 * a branch-based checkout.
113113 */
114- static int perform_checkout_ref (git_repository * repo , git_annotated_commit * target , checkout_options * opts )
114+ static int perform_checkout_ref (git_repository * repo , git_annotated_commit * target , const char * target_ref , checkout_options * opts )
115115{
116116 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT ;
117+ git_reference * ref = NULL , * branch = NULL ;
117118 git_commit * target_commit = NULL ;
118119 int err ;
119120
@@ -155,21 +156,93 @@ static int perform_checkout_ref(git_repository *repo, git_annotated_commit *targ
155156 * we might need to detach HEAD.
156157 */
157158 if (git_annotated_commit_ref (target )) {
158- err = git_repository_set_head (repo , git_annotated_commit_ref (target ));
159+ const char * target_head ;
160+
161+ if ((err = git_reference_lookup (& ref , repo , git_annotated_commit_ref (target ))) < 0 )
162+ goto error ;
163+
164+ if (git_reference_is_remote (ref )) {
165+ if ((err = git_branch_create_from_annotated (& branch , repo , target_ref , target , 0 )) < 0 )
166+ goto error ;
167+ target_head = git_reference_name (branch );
168+ } else {
169+ target_head = git_annotated_commit_ref (target );
170+ }
171+
172+ err = git_repository_set_head (repo , target_head );
159173 } else {
160174 err = git_repository_set_head_detached_from_annotated (repo , target );
161175 }
176+
177+ error :
162178 if (err != 0 ) {
163179 fprintf (stderr , "failed to update HEAD reference: %s\n" , git_error_last ()-> message );
164180 goto cleanup ;
165181 }
166182
167183cleanup :
168184 git_commit_free (target_commit );
185+ git_reference_free (branch );
186+ git_reference_free (ref );
169187
170188 return err ;
171189}
172190
191+ /**
192+ * This corresponds to `git switch --guess`: if a given ref does
193+ * not exist, git will by default try to guess the reference by
194+ * seeing whether any remote has a branch called <ref>. If there
195+ * is a single remote only that has it, then it is assumed to be
196+ * the desired reference and a local branch is created for it.
197+ *
198+ * The following is a simplified implementation. It will not try
199+ * to check whether the ref is unique across all remotes.
200+ */
201+ static int guess_refish (git_annotated_commit * * out , git_repository * repo , const char * ref )
202+ {
203+ git_strarray remotes = { NULL , 0 };
204+ git_reference * remote_ref = NULL ;
205+ int error ;
206+ size_t i ;
207+
208+ if ((error = git_remote_list (& remotes , repo )) < 0 )
209+ goto out ;
210+
211+ for (i = 0 ; i < remotes .count ; i ++ ) {
212+ char * refname = NULL ;
213+ size_t reflen ;
214+
215+ reflen = snprintf (refname , 0 , "refs/remotes/%s/%s" , remotes .strings [i ], ref );
216+ if ((refname = malloc (reflen + 1 )) == NULL ) {
217+ error = -1 ;
218+ goto next ;
219+ }
220+ snprintf (refname , reflen + 1 , "refs/remotes/%s/%s" , remotes .strings [i ], ref );
221+
222+ if ((error = git_reference_lookup (& remote_ref , repo , refname )) < 0 )
223+ goto next ;
224+
225+ break ;
226+ next :
227+ free (refname );
228+ if (error < 0 && error != GIT_ENOTFOUND )
229+ break ;
230+ }
231+
232+ if (!remote_ref ) {
233+ error = GIT_ENOTFOUND ;
234+ goto out ;
235+ }
236+
237+ if ((error = git_annotated_commit_from_ref (out , repo , remote_ref )) < 0 )
238+ goto out ;
239+
240+ out :
241+ git_reference_free (remote_ref );
242+ git_strarray_free (& remotes );
243+ return error ;
244+ }
245+
173246/** That example's entry point */
174247int lg2_checkout (git_repository * repo , int argc , char * * argv )
175248{
@@ -202,12 +275,12 @@ int lg2_checkout(git_repository *repo, int argc, char **argv)
202275 /**
203276 * Try to resolve a "refish" argument to a target libgit2 can use
204277 */
205- err = resolve_refish (& checkout_target , repo , args .argv [args .pos ]);
206- if (err != 0 ) {
278+ if (( err = resolve_refish (& checkout_target , repo , args .argv [args .pos ])) < 0 &&
279+ (err = guess_refish ( & checkout_target , repo , args . argv [ args . pos ])) < 0 ) {
207280 fprintf (stderr , "failed to resolve %s: %s\n" , args .argv [args .pos ], git_error_last ()-> message );
208281 goto cleanup ;
209282 }
210- err = perform_checkout_ref (repo , checkout_target , & opts );
283+ err = perform_checkout_ref (repo , checkout_target , args . argv [ args . pos ], & opts );
211284 }
212285
213286cleanup :
0 commit comments