Merge branch 'ps/ref-transaction-hook'

A new hook.

* ps/ref-transaction-hook:
  refs: implement reference transaction hook
This commit is contained in:
Junio C Hamano 2020-07-06 22:09:13 -07:00
commit 33a22c1a88
4 changed files with 244 additions and 2 deletions

View File

@ -404,6 +404,35 @@ Both standard output and standard error output are forwarded to
`git send-pack` on the other end, so you can simply `echo` messages
for the user.
ref-transaction
~~~~~~~~~~~~~~~
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
- "prepared": All reference updates have been queued to the
transaction and references were locked on disk.
- "committed": The reference transaction was committed and all
references now have their respective new value.
- "aborted": The reference transaction was aborted, no changes
were performed and the locks have been released.
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
<old-value> SP <new-value> SP <ref-name> LF
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
"aborted" state in that case.
push-to-checkout
~~~~~~~~~~~~~~~~

76
refs.c
View File

@ -9,6 +9,7 @@
#include "iterator.h"
#include "refs.h"
#include "refs/refs-internal.h"
#include "run-command.h"
#include "object-store.h"
#include "object.h"
#include "tag.h"
@ -16,6 +17,7 @@
#include "worktree.h"
#include "argv-array.h"
#include "repository.h"
#include "sigchain.h"
/*
* List of all available backends
@ -1986,10 +1988,65 @@ int ref_update_reject_duplicates(struct string_list *refnames,
return 0;
}
static const char hook_not_found;
static const char *hook;
static int run_transaction_hook(struct ref_transaction *transaction,
const char *state)
{
struct child_process proc = CHILD_PROCESS_INIT;
struct strbuf buf = STRBUF_INIT;
int ret = 0, i;
if (hook == &hook_not_found)
return ret;
if (!hook)
hook = find_hook("reference-transaction");
if (!hook) {
hook = &hook_not_found;
return ret;
}
argv_array_pushl(&proc.args, hook, state, NULL);
proc.in = -1;
proc.stdout_to_stderr = 1;
proc.trace2_hook_name = "reference-transaction";
ret = start_command(&proc);
if (ret)
return ret;
sigchain_push(SIGPIPE, SIG_IGN);
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
strbuf_addf(&buf, "%s %s %s\n",
oid_to_hex(&update->old_oid),
oid_to_hex(&update->new_oid),
update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE)
ret = -1;
break;
}
}
close(proc.in);
sigchain_pop(SIGPIPE);
strbuf_release(&buf);
ret |= finish_command(&proc);
return ret;
}
int ref_transaction_prepare(struct ref_transaction *transaction,
struct strbuf *err)
{
struct ref_store *refs = transaction->ref_store;
int ret;
switch (transaction->state) {
case REF_TRANSACTION_OPEN:
@ -2012,7 +2069,17 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
return -1;
}
return refs->be->transaction_prepare(refs, transaction, err);
ret = refs->be->transaction_prepare(refs, transaction, err);
if (ret)
return ret;
ret = run_transaction_hook(transaction, "prepared");
if (ret) {
ref_transaction_abort(transaction, err);
die(_("ref updates aborted by hook"));
}
return 0;
}
int ref_transaction_abort(struct ref_transaction *transaction,
@ -2036,6 +2103,8 @@ int ref_transaction_abort(struct ref_transaction *transaction,
break;
}
run_transaction_hook(transaction, "aborted");
ref_transaction_free(transaction);
return ret;
}
@ -2064,7 +2133,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,
break;
}
return refs->be->transaction_finish(refs, transaction, err);
ret = refs->be->transaction_finish(refs, transaction, err);
if (!ret)
run_transaction_hook(transaction, "committed");
return ret;
}
int refs_verify_refname_available(struct ref_store *refs,

32
t/perf/p1400-update-ref.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
test_description="Tests performance of update-ref"
. ./perf-lib.sh
test_perf_fresh_repo
test_expect_success "setup" '
test_commit PRE &&
test_commit POST &&
printf "create refs/heads/%d PRE\n" $(test_seq 1000) >create &&
printf "update refs/heads/%d POST PRE\n" $(test_seq 1000) >update &&
printf "delete refs/heads/%d POST\n" $(test_seq 1000) >delete
'
test_perf "update-ref" '
for i in $(test_seq 1000)
do
git update-ref refs/heads/branch PRE &&
git update-ref refs/heads/branch POST PRE &&
git update-ref -d refs/heads/branch
done
'
test_perf "update-ref --stdin" '
git update-ref --stdin <create &&
git update-ref --stdin <update &&
git update-ref --stdin <delete
'
test_done

109
t/t1416-ref-transaction-hooks.sh Executable file
View File

@ -0,0 +1,109 @@
#!/bin/sh
test_description='reference transaction hooks'
. ./test-lib.sh
test_expect_success setup '
mkdir -p .git/hooks &&
test_commit PRE &&
test_commit POST &&
POST_OID=$(git rev-parse POST)
'
test_expect_success 'hook allows updating ref if successful' '
test_when_finished "rm .git/hooks/reference-transaction" &&
git reset --hard PRE &&
write_script .git/hooks/reference-transaction <<-\EOF &&
echo "$*" >>actual
EOF
cat >expect <<-EOF &&
prepared
committed
EOF
git update-ref HEAD POST &&
test_cmp expect actual
'
test_expect_success 'hook aborts updating ref in prepared state' '
test_when_finished "rm .git/hooks/reference-transaction" &&
git reset --hard PRE &&
write_script .git/hooks/reference-transaction <<-\EOF &&
if test "$1" = prepared
then
exit 1
fi
EOF
test_must_fail git update-ref HEAD POST 2>err &&
test_i18ngrep "ref updates aborted by hook" err
'
test_expect_success 'hook gets all queued updates in prepared state' '
test_when_finished "rm .git/hooks/reference-transaction actual" &&
git reset --hard PRE &&
write_script .git/hooks/reference-transaction <<-\EOF &&
if test "$1" = prepared
then
while read -r line
do
printf "%s\n" "$line"
done >actual
fi
EOF
cat >expect <<-EOF &&
$ZERO_OID $POST_OID HEAD
$ZERO_OID $POST_OID refs/heads/master
EOF
git update-ref HEAD POST <<-EOF &&
update HEAD $ZERO_OID $POST_OID
update refs/heads/master $ZERO_OID $POST_OID
EOF
test_cmp expect actual
'
test_expect_success 'hook gets all queued updates in committed state' '
test_when_finished "rm .git/hooks/reference-transaction actual" &&
git reset --hard PRE &&
write_script .git/hooks/reference-transaction <<-\EOF &&
if test "$1" = committed
then
while read -r line
do
printf "%s\n" "$line"
done >actual
fi
EOF
cat >expect <<-EOF &&
$ZERO_OID $POST_OID HEAD
$ZERO_OID $POST_OID refs/heads/master
EOF
git update-ref HEAD POST &&
test_cmp expect actual
'
test_expect_success 'hook gets all queued updates in aborted state' '
test_when_finished "rm .git/hooks/reference-transaction actual" &&
git reset --hard PRE &&
write_script .git/hooks/reference-transaction <<-\EOF &&
if test "$1" = aborted
then
while read -r line
do
printf "%s\n" "$line"
done >actual
fi
EOF
cat >expect <<-EOF &&
$ZERO_OID $POST_OID HEAD
$ZERO_OID $POST_OID refs/heads/master
EOF
git update-ref --stdin <<-EOF &&
start
update HEAD POST $ZERO_OID
update refs/heads/master POST $ZERO_OID
abort
EOF
test_cmp expect actual
'
test_done