Skip to content

Commit 8e2dc6a

Browse files
committed
commit: give final warning when reattaching HEAD to leave commits behind
You can detach the HEAD at an arbitrary commit in order to browse the files in various points in the history or build older versions of the software, without recording any new commit, and come back to an existing branch. When used in this "sightseer" mode, detached HEAD is a perfectly safe mechanism. It also is a useful state to experiment with throw-away commits. When coming back to an existing branch with "git checkout master", however, the commits that were created on the detached HEAD will become unreachable from anywhere but the reflog of the HEAD. Check if the commit we are about to leave is connected to some ref, and give a final warning otherwise to remind the user for safety. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7ed863a commit 8e2dc6a

File tree

1 file changed

+95
-6
lines changed

1 file changed

+95
-6
lines changed

builtin/checkout.c

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,100 @@ static void update_refs_for_switch(struct checkout_opts *opts,
578578
report_tracking(new);
579579
}
580580

581+
struct rev_list_args {
582+
int argc;
583+
int alloc;
584+
const char **argv;
585+
};
586+
587+
static void add_one_rev_list_arg(struct rev_list_args *args, const char *s)
588+
{
589+
ALLOC_GROW(args->argv, args->argc + 1, args->alloc);
590+
args->argv[args->argc++] = s;
591+
}
592+
593+
static int add_one_ref_to_rev_list_arg(const char *refname,
594+
const unsigned char *sha1,
595+
int flags,
596+
void *cb_data)
597+
{
598+
add_one_rev_list_arg(cb_data, refname);
599+
return 0;
600+
}
601+
602+
603+
static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
604+
{
605+
struct pretty_print_context ctx = { 0 };
606+
607+
parse_commit(commit);
608+
strbuf_addstr(sb, " - ");
609+
pretty_print_commit(CMIT_FMT_ONELINE, commit, sb, &ctx);
610+
strbuf_addch(sb, '\n');
611+
}
612+
613+
#define ORPHAN_CUTOFF 4
614+
static void suggest_reattach(struct commit *commit, struct rev_info *revs)
615+
{
616+
struct commit *c, *last = NULL;
617+
struct strbuf sb = STRBUF_INIT;
618+
int lost = 0;
619+
while ((c = get_revision(revs)) != NULL) {
620+
if (lost < ORPHAN_CUTOFF)
621+
describe_one_orphan(&sb, c);
622+
last = c;
623+
lost++;
624+
}
625+
if (ORPHAN_CUTOFF < lost) {
626+
int more = lost - ORPHAN_CUTOFF;
627+
if (more == 1)
628+
describe_one_orphan(&sb, last);
629+
else
630+
strbuf_addf(&sb, " ... and %d more.\n", more);
631+
}
632+
633+
fprintf(stderr,
634+
"Warning: you are leaving %d commit%s behind, "
635+
"not connected to\n"
636+
"any of your branches:\n\n"
637+
"%s\n"
638+
"If you want to keep them by creating a new branch, "
639+
"this may be a good time\nto do so with:\n\n"
640+
" git branch new_branch_name %s\n\n",
641+
lost, ((1 < lost) ? "s" : ""),
642+
sb.buf,
643+
sha1_to_hex(commit->object.sha1));
644+
strbuf_release(&sb);
645+
}
646+
647+
/*
648+
* We are about to leave commit that was at the tip of a detached
649+
* HEAD. If it is not reachable from any ref, this is the last chance
650+
* for the user to do so without resorting to reflog.
651+
*/
652+
static void orphaned_commit_warning(struct commit *commit)
653+
{
654+
struct rev_list_args args = { 0, 0, NULL };
655+
struct rev_info revs;
656+
657+
add_one_rev_list_arg(&args, "(internal)");
658+
add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1));
659+
add_one_rev_list_arg(&args, "--not");
660+
for_each_ref(add_one_ref_to_rev_list_arg, &args);
661+
add_one_rev_list_arg(&args, "--");
662+
add_one_rev_list_arg(&args, NULL);
663+
664+
init_revisions(&revs, NULL);
665+
if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
666+
die("internal error: only -- alone should have been left");
667+
if (prepare_revision_walk(&revs))
668+
die("internal error in revision walk");
669+
if (!(commit->object.flags & UNINTERESTING))
670+
suggest_reattach(commit, &revs);
671+
else
672+
describe_detached_head("Previous HEAD position was", commit);
673+
}
674+
581675
static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
582676
{
583677
int ret = 0;
@@ -605,13 +699,8 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
605699
if (ret)
606700
return ret;
607701

608-
/*
609-
* If we were on a detached HEAD, but have now moved to
610-
* a new commit, we want to mention the old commit once more
611-
* to remind the user that it might be lost.
612-
*/
613702
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
614-
describe_detached_head("Previous HEAD position was", old.commit);
703+
orphaned_commit_warning(old.commit);
615704

616705
update_refs_for_switch(opts, &old, new);
617706

0 commit comments

Comments
 (0)