Merge branch 'ps/ref-transaction-hook' into jch
* ps/ref-transaction-hook: refs: implement reference transaction hook
This commit is contained in:
@ -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
|
`git send-pack` on the other end, so you can simply `echo` messages
|
||||||
for the user.
|
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
|
push-to-checkout
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
76
refs.c
76
refs.c
@ -9,6 +9,7 @@
|
|||||||
#include "iterator.h"
|
#include "iterator.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "refs/refs-internal.h"
|
#include "refs/refs-internal.h"
|
||||||
|
#include "run-command.h"
|
||||||
#include "object-store.h"
|
#include "object-store.h"
|
||||||
#include "object.h"
|
#include "object.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
@ -16,6 +17,7 @@
|
|||||||
#include "worktree.h"
|
#include "worktree.h"
|
||||||
#include "argv-array.h"
|
#include "argv-array.h"
|
||||||
#include "repository.h"
|
#include "repository.h"
|
||||||
|
#include "sigchain.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* List of all available backends
|
* List of all available backends
|
||||||
@ -1986,10 +1988,65 @@ int ref_update_reject_duplicates(struct string_list *refnames,
|
|||||||
return 0;
|
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,
|
int ref_transaction_prepare(struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
struct ref_store *refs = transaction->ref_store;
|
struct ref_store *refs = transaction->ref_store;
|
||||||
|
int ret;
|
||||||
|
|
||||||
switch (transaction->state) {
|
switch (transaction->state) {
|
||||||
case REF_TRANSACTION_OPEN:
|
case REF_TRANSACTION_OPEN:
|
||||||
@ -2012,7 +2069,17 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
|
|||||||
return -1;
|
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,
|
int ref_transaction_abort(struct ref_transaction *transaction,
|
||||||
@ -2036,6 +2103,8 @@ int ref_transaction_abort(struct ref_transaction *transaction,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_transaction_hook(transaction, "aborted");
|
||||||
|
|
||||||
ref_transaction_free(transaction);
|
ref_transaction_free(transaction);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -2064,7 +2133,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,
|
|||||||
break;
|
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,
|
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
|
Reference in New Issue
Block a user