From: Junio C Hamano Date: Wed, 27 Dec 2017 19:16:21 +0000 (-0800) Subject: Merge branch 'es/worktree-checkout-hook' X-Git-Tag: v2.16.0-rc0~34 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/e87f9fc9d4ead0ec7f98c9493a9f2ad8c41cd6ee?hp=-c Merge branch 'es/worktree-checkout-hook' "git worktree add" learned to run the post-checkout hook, just like "git checkout" does, after the initial checkout. * es/worktree-checkout-hook: worktree: invoke post-checkout hook (unless --no-checkout) --- e87f9fc9d4ead0ec7f98c9493a9f2ad8c41cd6ee diff --combined Documentation/githooks.txt index b63f2ea860,91eb297f7b..f877f7b7cd --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@@ -170,7 -170,8 +170,8 @@@ This hook cannot affect the outcome of It is also run after 'git clone', unless the --no-checkout (-n) option is used. The first parameter given to the hook is the null-ref, the second the - ref of the new HEAD and the flag is always 1. + ref of the new HEAD and the flag is always 1. Likewise for 'git worktree add' + unless --no-checkout is used. This hook can be used to perform repository validity checks, auto-display differences from the previous HEAD if different, or set working dir metadata @@@ -223,8 -224,8 +224,8 @@@ to the user by writing to standard erro pre-receive ~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. Just before starting to update refs on the remote repository, the pre-receive hook is invoked. Its exit status determines the success or failure of the update. @@@ -264,8 -265,8 +265,8 @@@ linkgit:git-receive-pack[1] for some ca update ~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. @@@ -309,8 -310,8 +310,8 @@@ unannotated tags to be pushed post-receive ~~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. It executes on the remote repository once after all the refs have been updated. @@@ -348,8 -349,8 +349,8 @@@ will be set to zero, `GIT_PUSH_OPTION_C post-update ~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. It executes on the remote repository once after all the refs have been updated. @@@ -379,8 -380,8 +380,8 @@@ for the user push-to-checkout ~~~~~~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository, when +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository, and when the push tries to update the branch that is currently checked out and the `receive.denyCurrentBranch` configuration variable is set to `updateInstead`. Such a push by default is refused if the working diff --combined builtin/worktree.c index 002a569a11,9591f10442..7cef5b120b --- a/builtin/worktree.c +++ b/builtin/worktree.c @@@ -1,5 -1,4 +1,5 @@@ #include "cache.h" +#include "checkout.h" #include "config.h" #include "builtin.h" #include "dir.h" @@@ -33,19 -32,8 +33,19 @@@ struct add_opts static int show_only; static int verbose; +static int guess_remote; static timestamp_t expire; +static int git_worktree_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "worktree.guessremote")) { + guess_remote = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + static int prune_worktree(const char *id, struct strbuf *reason) { struct stat st; @@@ -230,20 -218,21 +230,21 @@@ static int add_worktree(const char *pat int counter = 0, len, ret; struct strbuf symref = STRBUF_INIT; struct commit *commit = NULL; + int is_branch = 0; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && - ref_exists(symref.buf)) { /* it's a branch */ + ref_exists(symref.buf)) { + is_branch = 1; if (!opts->force) die_if_checked_out(symref.buf, 0); - } else { /* must be a commit */ - commit = lookup_commit_reference_by_name(refname); - if (!commit) - die(_("invalid reference: %s"), refname); } + commit = lookup_commit_reference_by_name(refname); + if (!commit) + die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name); @@@ -308,7 -297,7 +309,7 @@@ argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; - if (commit) + if (!is_branch) argv_array_pushl(&cp.args, "update-ref", "HEAD", oid_to_hex(&commit->object.oid), NULL); else @@@ -339,6 -328,15 +340,15 @@@ done strbuf_addf(&sb, "%s/locked", sb_repo.buf); unlink_or_warn(sb.buf); } + + /* + * Hook failure does not warrant worktree deletion, so run hook after + * is_junk is cleared, but do return appropriate code when hook fails. + */ + if (!ret && opts->checkout) + ret = run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid), + oid_to_hex(&commit->object.oid), "1", NULL); + argv_array_clear(&child_env); strbuf_release(&sb); strbuf_release(&symref); @@@ -353,7 -351,6 +363,7 @@@ static int add(int ac, const char **av const char *new_branch_force = NULL; char *path; const char *branch; + const char *opt_track = NULL; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout even if already checked out in other worktree")), OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), @@@ -363,11 -360,6 +373,11 @@@ OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")), + OPT_PASSTHRU(0, "track", &opt_track, NULL, + N_("set up tracking mode (see git-branch(1))"), + PARSE_OPT_NOARG | PARSE_OPT_OPTARG), + OPT_BOOL(0, "guess-remote", &guess_remote, + N_("try to match the new branch name with a remote-tracking branch")), OPT_END() }; @@@ -402,28 -394,6 +412,28 @@@ int n; const char *s = worktree_basename(path, &n); opts.new_branch = xstrndup(s, n); + if (guess_remote) { + struct object_id oid; + const char *remote = + unique_tracking_name(opts.new_branch, &oid); + if (remote) + branch = remote; + } + } + + if (ac == 2 && !opts.new_branch && !opts.detach) { + struct object_id oid; + struct commit *commit; + const char *remote; + + commit = lookup_commit_reference_by_name(branch); + if (!commit) { + remote = unique_tracking_name(branch, &oid); + if (remote) { + opts.new_branch = branch; + branch = remote; + } + } } if (opts.new_branch) { @@@ -434,13 -404,9 +444,13 @@@ argv_array_push(&cp.args, "--force"); argv_array_push(&cp.args, opts.new_branch); argv_array_push(&cp.args, branch); + if (opt_track) + argv_array_push(&cp.args, opt_track); if (run_command(&cp)) return -1; branch = opts.new_branch; + } else if (opt_track) { + die(_("--[no-]track can only be used if a new branch is created")); } UNLEAK(path); @@@ -601,7 -567,7 +611,7 @@@ int cmd_worktree(int ac, const char **a OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_worktree_config, NULL); if (ac < 2) usage_with_options(worktree_usage, options); diff --combined t/t2025-worktree-add.sh index 6ce9b9c070,d6fb062f83..1285668cfc --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@@ -313,135 -313,34 +313,164 @@@ test_expect_success 'checkout a branch test_expect_success 'rename a branch under bisect not allowed' ' test_must_fail git branch -M under-bisect bisect-with-new-name ' +# Is branch "refs/heads/$1" set to pull from "$2/$3"? +test_branch_upstream () { + printf "%s\n" "$2" "refs/heads/$3" >expect.upstream && + { + git config "branch.$1.remote" && + git config "branch.$1.merge" + } >actual.upstream && + test_cmp expect.upstream actual.upstream +} + +test_expect_success '--track sets up tracking' ' + test_when_finished rm -rf track && + git worktree add --track -b track track master && + test_branch_upstream track . master +' + +# setup remote repository $1 and repository $2 with $1 set up as +# remote. The remote has two branches, master and foo. +setup_remote_repo () { + git init $1 && + ( + cd $1 && + test_commit $1_master && + git checkout -b foo && + test_commit upstream_foo + ) && + git init $2 && + ( + cd $2 && + test_commit $2_master && + git remote add $1 ../$1 && + git config remote.$1.fetch \ + "refs/heads/*:refs/remotes/$1/*" && + git fetch --all + ) +} + +test_expect_success '--no-track avoids setting up tracking' ' + test_when_finished rm -rf repo_upstream repo_local foo && + setup_remote_repo repo_upstream repo_local && + ( + cd repo_local && + git worktree add --no-track -b foo ../foo repo_upstream/foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo + ) +' + +test_expect_success '"add" fails' ' + test_must_fail git worktree add foo non-existent +' + +test_expect_success '"add" dwims' ' + test_when_finished rm -rf repo_upstream repo_dwim foo && + setup_remote_repo repo_upstream repo_dwim && + git init repo_dwim && + ( + cd repo_dwim && + git worktree add ../foo foo + ) && + ( + cd foo && + test_branch_upstream foo repo_upstream foo && + test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree add does not match remote' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git worktree add ../foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree add --guess-remote sets up tracking' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git worktree add --guess-remote ../foo + ) && + ( + cd foo && + test_branch_upstream foo repo_a foo && + test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git config worktree.guessRemote true && + git worktree add ../foo + ) && + ( + cd foo && + test_branch_upstream foo repo_a foo && + test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree --no-guess-remote option overrides config' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git config worktree.guessRemote true && + git worktree add --no-guess-remote ../foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + post_checkout_hook () { + test_when_finished "rm -f .git/hooks/post-checkout" && + mkdir -p .git/hooks && + write_script .git/hooks/post-checkout <<-\EOF + echo $* >hook.actual + EOF + } + + test_expect_success '"add" invokes post-checkout hook (branch)' ' + post_checkout_hook && + printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect && + git worktree add gumby && + test_cmp hook.expect hook.actual + ' + + test_expect_success '"add" invokes post-checkout hook (detached)' ' + post_checkout_hook && + printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect && + git worktree add --detach grumpy && + test_cmp hook.expect hook.actual + ' + + test_expect_success '"add --no-checkout" suppresses post-checkout hook' ' + post_checkout_hook && + rm -f hook.actual && + git worktree add --no-checkout gloopy && + test_path_is_missing hook.actual + ' + test_done