@matheusbsbs / matheus-bagliano / commits / 8e2dc6ac06
Fork 0

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 <gitster@pobox.com>

Junio C Hamano committed Feb 18, 2011 at 16:04 UTC 8e2dc6ac06ae90a00965e5c00aa4918dfd447639
1 file changed +95 -6
builtin/checkout.c
+95 -6
@@ -578,6 +578,100 @@ static void update_refs_for_switch(struct checkout_opts *opts,
578 report_tracking(new);
579 }
580
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 +
675 static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
676 {
677 int ret = 0;
@@ -605,13 +699,8 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
699 if (ret)
700 return ret;
701
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 - */
702 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);
704
705 update_refs_for_switch(opts, &old, new);
706