From: "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: "Junio C Hamano" <gitster@pobox.com>,
"Christian Couder" <christian.couder@gmail.com>,
"Hariom Verma" <hariom18599@gmail.com>,
"brian m. carlson" <sandals@crustytoothpaste.net>,
"Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>,
"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>,
"Eric Sunshine" <sunshine@sunshineco.com>,
"ZheNing Hu" <adlternative@gmail.com>,
"ZheNing Hu" <adlternative@gmail.com>
Subject: [PATCH 2/2] checkout: introduce "--to-branch" option
Date: Fri, 10 Dec 2021 06:22:09 +0000 [thread overview]
Message-ID: <254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.1095.git.1639117329.gitgitgadget@gmail.com>
From: ZheNing Hu <adlternative@gmail.com>
When we want checkout to a branch (e.g. dev1) which reference
to a commit, but sometimes we only remember the tag (e.g. v1.1)
on it, we will use `git checkout v1.1` to find the commit first,
git will be in the state of deatching HEAD, so we have to search the
branches on the commit and checkout the branch we perfer. This will
be a bit cumbersome.
Introduce "--to-branch" option, `git checkout --to-branch <tag>`
and `git checkout --to-branch <commit>` will search all branches
and find a unique branch reference to the commit (or the commit which
the tag reference to) and checkout to it. If the commit have more
than one branches, it will report error "here are more than one
branch on commit".
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
Documentation/git-checkout.txt | 8 +++-
builtin/checkout.c | 33 +++++++++++++
t/t2018-checkout-branch.sh | 85 ++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
4 files changed, 126 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index d473c9bf387..2a240699fd9 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -10,7 +10,7 @@ SYNOPSIS
[verse]
'git checkout' [-q] [-f] [-m] [<branch>]
'git checkout' [-q] [-f] [-m] --detach [<branch>]
-'git checkout' [-q] [-f] [-m] [--detach] <commit>
+'git checkout' [-q] [-f] [-m] [--detach] [-w|--to-branch] <commit>
'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]
@@ -210,6 +210,12 @@ variable.
`<commit>` is not a branch name. See the "DETACHED HEAD" section
below for details.
+-w::
+--to-branch::
+ Rather than checking out a commit to work on it, checkout out
+ to the unique branch on it. If there are multiple branches on
+ the commit, the checkout will fail.
+
--orphan <new_branch>::
Create a new 'orphan' branch, named `<new_branch>`, started from
`<start_point>` and switch to it. The first commit made on this
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3eeac3147f2..696248b05c0 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -28,6 +28,7 @@
#include "xdiff-interface.h"
#include "entry.h"
#include "parallel-checkout.h"
+#include "../ref-filter.h"
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
@@ -70,6 +71,7 @@ struct checkout_opts {
int empty_pathspec_ok;
int checkout_index;
int checkout_worktree;
+ int to_branch;
const char *ignore_unmerged_opt;
int ignore_unmerged;
int pathspec_file_nul;
@@ -1512,6 +1514,35 @@ static int checkout_branch(struct checkout_opts *opts,
(flag & REF_ISSYMREF) && is_null_oid(&rev))
return switch_unborn_to_new_branch(opts);
}
+ if (opts->to_branch) {
+ struct ref_filter filter;
+ struct ref_array array;
+ int i;
+ int count = 0;
+ const char *unused_pattern = NULL;
+
+ memset(&array, 0, sizeof(array));
+ memset(&filter, 0, sizeof(filter));
+ filter.kind = FILTER_REFS_BRANCHES;
+ filter.name_patterns = &unused_pattern;
+ filter_refs(&array, &filter, filter.kind);
+ for (i = 0; i < array.nr; i++) {
+ if (oideq(&array.items[i]->objectname, &new_branch_info->oid)) {
+ if (count)
+ die(_("here are more than one branch on commit %s"), oid_to_hex(&new_branch_info->oid));
+ count++;
+ if (new_branch_info->refname)
+ free((char *)new_branch_info->refname);
+ new_branch_info->refname = xstrdup(array.items[i]->refname);
+ if (new_branch_info->path)
+ free((char *)new_branch_info->path);
+ new_branch_info->path = xstrdup(array.items[i]->refname);
+ new_branch_info->name = new_branch_info->path;
+ }
+ }
+ ref_array_clear(&array);
+ }
+
return switch_branches(opts, new_branch_info);
}
@@ -1797,6 +1828,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
N_("second guess 'git checkout <no-such-branch>' (default)")),
+ OPT_BOOL('w', "to-branch", &opts.to_branch,
+ N_("checkout to a branch from a commit or a tag")),
OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
OPT_END()
};
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
index 93be1c0eae5..53e45cfe7fd 100755
--- a/t/t2018-checkout-branch.sh
+++ b/t/t2018-checkout-branch.sh
@@ -270,4 +270,89 @@ test_expect_success 'checkout -b rejects an extra path argument' '
test_i18ngrep "Cannot update paths and switch to branch" err
'
+test_expect_success 'checkout -w with oid' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git branch -M main &&
+ test_commit initial file1 &&
+ HEAD1=$(git rev-parse --verify HEAD) &&
+ git switch -c dev &&
+ test_commit step2 file2 &&
+ HEAD2=$(git rev-parse --verify HEAD) &&
+
+ git checkout -w $HEAD1 &&
+
+ echo "refs/heads/main" >ref.expect &&
+ git rev-parse --symbolic-full-name HEAD >ref.actual &&
+ test_cmp ref.expect ref.actual &&
+
+ echo "$HEAD1" >oid.expect &&
+ git rev-parse --verify HEAD >oid.actual &&
+ test_cmp oid.expect oid.actual &&
+
+ git checkout -w $HEAD2 &&
+
+ echo "refs/heads/dev" >ref.expect &&
+ git rev-parse --symbolic-full-name HEAD >ref.actual &&
+ test_cmp ref.expect ref.actual &&
+
+ echo "$HEAD2" >oid.expect &&
+ git rev-parse --verify HEAD >oid.actual &&
+ test_cmp oid.expect oid.actual
+ )
+'
+
+test_expect_success 'checkout -w with tag' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git branch -M main &&
+ test_commit initial file1 &&
+ git tag v1 &&
+ HEAD1=$(git rev-parse --verify HEAD) &&
+ git switch -c dev &&
+ test_commit step2 file2 &&
+ git tag v2 &&
+ HEAD2=$(git rev-parse --verify HEAD) &&
+
+ git checkout -w v1 &&
+
+ echo "refs/heads/main" >ref.expect &&
+ git rev-parse --symbolic-full-name HEAD >ref.actual &&
+ test_cmp ref.expect ref.actual &&
+
+ echo "$HEAD1" >oid.expect &&
+ git rev-parse --verify HEAD >oid.actual &&
+ test_cmp oid.expect oid.actual &&
+
+ git checkout -w v2 &&
+
+ echo "refs/heads/dev" >ref.expect &&
+ git rev-parse --symbolic-full-name HEAD >ref.actual &&
+ test_cmp ref.expect ref.actual &&
+
+ echo "$HEAD2" >oid.expect &&
+ git rev-parse --verify HEAD >oid.actual &&
+ test_cmp oid.expect oid.actual
+ )
+'
+
+test_expect_success 'checkout -w with branches' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git branch -M main &&
+ test_commit initial file1 &&
+ HEAD1=$(git rev-parse --verify HEAD) &&
+ git tag v1 &&
+ git branch dev &&
+ test_must_fail git checkout -w $HEAD1 &&
+ test_must_fail git checkout -w dev
+ )
+'
+
test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 518203fbe07..4ec134338c2 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2021,6 +2021,7 @@ test_expect_success 'double dash "git checkout"' '
--orphan=Z
--ours Z
--theirs Z
+ --to-branch Z
--merge Z
--conflict=Z
--patch Z
--
gitgitgadget
next prev parent reply other threads:[~2021-12-10 6:22 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-12-10 6:22 [PATCH 0/2] checkout: introduce "--to-branch" option ZheNing Hu via GitGitGadget
2021-12-10 6:22 ` [PATCH 1/2] checkout: handling branch_info memory leak ZheNing Hu via GitGitGadget
2021-12-10 7:13 ` Ævar Arnfjörð Bjarmason
2021-12-10 6:22 ` ZheNing Hu via GitGitGadget [this message]
2021-12-10 8:34 ` [PATCH 2/2] checkout: introduce "--to-branch" option Christian Couder
2021-12-11 6:34 ` ZheNing Hu
2021-12-10 8:59 ` Ævar Arnfjörð Bjarmason
2021-12-11 7:11 ` ZheNing Hu
2021-12-10 11:51 ` Bagas Sanjaya
2021-12-11 7:12 ` ZheNing Hu
2021-12-10 22:14 ` Junio C Hamano
2021-12-11 7:51 ` ZheNing Hu
2021-12-12 18:46 ` Junio C Hamano
2021-12-13 19:55 ` Ævar Arnfjörð Bjarmason
2021-12-13 21:36 ` Junio C Hamano
2021-12-13 21:52 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=adlternative@gmail.com \
--cc=avarab@gmail.com \
--cc=christian.couder@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=hariom18599@gmail.com \
--cc=pclouds@gmail.com \
--cc=sandals@crustytoothpaste.net \
--cc=sunshine@sunshineco.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).