worktree: invoke post-checkout hook (unless --no-checkout)

git-clone and git-checkout both invoke the post-checkout hook following
a successful checkout, yet git-worktree neglects to do so even though it
too "checks out" the worktree. Fix this oversight.

Implementation note: The newly-created worktree may reference a branch
or be detached. In the latter case, a commit lookup is performed, though
the result is used only in a boolean sense to (a) determine if the
commit actually exists, and (b) assign either the branch name or commit
ID to HEAD. Since the post-commit hook needs to know the ID of the
checked-out commit, the lookup now needs to be done in all cases, rather
than only when detached. Consequently, a new boolean is needed to handle
(b) since the lookup result itself can no longer perform that role.

Reported-by: Matthew K Gumbel <matthew.k.gumbel@intel.com>
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Eric Sunshine 2017-12-07 16:20:17 -05:00 committed by Junio C Hamano
parent 95ec6b1b33
commit ade546be47
3 changed files with 47 additions and 7 deletions

View file

@ -170,7 +170,8 @@ This hook cannot affect the outcome of 'git checkout'.
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

View file

@ -218,20 +218,21 @@ static int add_worktree(const char *path, const char *refname,
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);
@ -296,7 +297,7 @@ static int add_worktree(const char *path, const char *refname,
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
@ -327,6 +328,15 @@ static int add_worktree(const char *path, const char *refname,
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);

View file

@ -314,4 +314,33 @@ test_expect_success 'rename a branch under bisect not allowed' '
test_must_fail git branch -M under-bisect bisect-with-new-name
'
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