Merge branch 'ps/ref-transaction-hook'
A new hook. * ps/ref-transaction-hook: refs: implement reference transaction hook
This commit is contained in:
commit
33a22c1a88
@ -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
76
refs.c
@ -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
32
t/perf/p1400-update-ref.sh
Executable 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
109
t/t1416-ref-transaction-hooks.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user