Skip to content

Commit 31e8595

Browse files
jiangxingitster
authored andcommitted
receive-pack: new config receive.procReceiveRefs
Add a new multi-valued config variable "receive.procReceiveRefs" for `receive-pack` command, like the follows: git config --system --add receive.procReceiveRefs refs/for git config --system --add receive.procReceiveRefs refs/drafts If the specific prefix strings given by the config variables match the reference names of the commands which are sent from git client to `receive-pack`, these commands will be executed by an external hook (named "proc-receive"), instead of the internal `execute_commands` function. For example, if it is set to "refs/for", pushing to a reference such as "refs/for/master" will not create or update reference "refs/for/master", but may create or update a pull request directly by running the hook "proc-receive". Optional modifiers can be provided in the beginning of the value to filter commands for specific actions: create (a), modify (m), delete (d). A `!` can be included in the modifiers to negate the reference prefix entry. E.g.: git config --system --add receive.procReceiveRefs ad:refs/heads git config --system --add receive.procReceiveRefs !:refs/heads Signed-off-by: Jiang Xin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b913075 commit 31e8595

7 files changed

+494
-7
lines changed

Documentation/config/receive.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,28 @@ receive.hideRefs::
114114
An attempt to update or delete a hidden ref by `git push` is
115115
rejected.
116116

117+
receive.procReceiveRefs::
118+
This is a multi-valued variable that defines reference prefixes
119+
to match the commands in `receive-pack`. Commands matching the
120+
prefixes will be executed by an external hook "proc-receive",
121+
instead of the internal `execute_commands` function. If this
122+
variable is not defined, the "proc-receive" hook will never be
123+
used, and all commands will be executed by the internal
124+
`execute_commands` function.
125+
+
126+
For example, if this variable is set to "refs/for", pushing to reference
127+
such as "refs/for/master" will not create or update a reference named
128+
"refs/for/master", but may create or update a pull request directly by
129+
running the hook "proc-receive".
130+
+
131+
Optional modifiers can be provided in the beginning of the value to filter
132+
commands for specific actions: create (a), modify (m), delete (d).
133+
A `!` can be included in the modifiers to negate the reference prefix entry.
134+
E.g.:
135+
+
136+
git config --system --add receive.procReceiveRefs ad:refs/heads
137+
git config --system --add receive.procReceiveRefs !:refs/heads
138+
117139
receive.updateServerInfo::
118140
If set to true, git-receive-pack will run git-update-server-info
119141
after receiving data from git-push and updating refs.

builtin/receive-pack.c

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ static int keepalive_in_sec = 5;
9797

9898
static struct tmp_objdir *tmp_objdir;
9999

100+
static struct proc_receive_ref {
101+
unsigned int want_add:1,
102+
want_delete:1,
103+
want_modify:1,
104+
negative_ref:1;
105+
char *ref_prefix;
106+
struct proc_receive_ref *next;
107+
} *proc_receive_ref;
108+
109+
static void proc_receive_ref_append(const char *prefix);
110+
100111
static enum deny_action parse_deny_action(const char *var, const char *value)
101112
{
102113
if (value) {
@@ -229,6 +240,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
229240
return 0;
230241
}
231242

243+
if (strcmp(var, "receive.procreceiverefs") == 0) {
244+
if (!value)
245+
return config_error_nonbool(var);
246+
proc_receive_ref_append(value);
247+
return 0;
248+
}
249+
232250
return git_default_config(var, value, cb);
233251
}
234252

@@ -324,6 +342,79 @@ struct command {
324342
char ref_name[FLEX_ARRAY]; /* more */
325343
};
326344

345+
static void proc_receive_ref_append(const char *prefix)
346+
{
347+
struct proc_receive_ref *ref_pattern;
348+
char *p;
349+
int len;
350+
351+
ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));
352+
p = strchr(prefix, ':');
353+
if (p) {
354+
while (prefix < p) {
355+
if (*prefix == 'a')
356+
ref_pattern->want_add = 1;
357+
else if (*prefix == 'd')
358+
ref_pattern->want_delete = 1;
359+
else if (*prefix == 'm')
360+
ref_pattern->want_modify = 1;
361+
else if (*prefix == '!')
362+
ref_pattern->negative_ref = 1;
363+
prefix++;
364+
}
365+
prefix++;
366+
} else {
367+
ref_pattern->want_add = 1;
368+
ref_pattern->want_delete = 1;
369+
ref_pattern->want_modify = 1;
370+
}
371+
len = strlen(prefix);
372+
while (len && prefix[len - 1] == '/')
373+
len--;
374+
ref_pattern->ref_prefix = xmemdupz(prefix, len);
375+
if (!proc_receive_ref) {
376+
proc_receive_ref = ref_pattern;
377+
} else {
378+
struct proc_receive_ref *end;
379+
380+
end = proc_receive_ref;
381+
while (end->next)
382+
end = end->next;
383+
end->next = ref_pattern;
384+
}
385+
}
386+
387+
static int proc_receive_ref_matches(struct command *cmd)
388+
{
389+
struct proc_receive_ref *p;
390+
391+
if (!proc_receive_ref)
392+
return 0;
393+
394+
for (p = proc_receive_ref; p; p = p->next) {
395+
const char *match = p->ref_prefix;
396+
const char *remains;
397+
398+
if (!p->want_add && is_null_oid(&cmd->old_oid))
399+
continue;
400+
else if (!p->want_delete && is_null_oid(&cmd->new_oid))
401+
continue;
402+
else if (!p->want_modify &&
403+
!is_null_oid(&cmd->old_oid) &&
404+
!is_null_oid(&cmd->new_oid))
405+
continue;
406+
407+
if (skip_prefix(cmd->ref_name, match, &remains) &&
408+
(!*remains || *remains == '/')) {
409+
if (!p->negative_ref)
410+
return 1;
411+
} else if (p->negative_ref) {
412+
return 1;
413+
}
414+
}
415+
return 0;
416+
}
417+
327418
static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
328419
static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
329420

@@ -1812,14 +1903,15 @@ static void execute_commands(struct command *commands,
18121903
* Try to find commands that have special prefix in their reference names,
18131904
* and mark them to run an external "proc-receive" hook later.
18141905
*/
1815-
for (cmd = commands; cmd; cmd = cmd->next) {
1816-
if (!should_process_cmd(cmd))
1817-
continue;
1906+
if (proc_receive_ref) {
1907+
for (cmd = commands; cmd; cmd = cmd->next) {
1908+
if (!should_process_cmd(cmd))
1909+
continue;
18181910

1819-
/* TODO: replace the fixed prefix by looking up git config variables. */
1820-
if (!strncmp(cmd->ref_name, "refs/for/", 9)) {
1821-
cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
1822-
run_proc_receive = 1;
1911+
if (proc_receive_ref_matches(cmd)) {
1912+
cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
1913+
run_proc_receive = 1;
1914+
}
18231915
}
18241916
}
18251917

t/t5411/once-0010-report-status-v1.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
test_expect_success "setup receive.procReceiveRefs" '
2+
git -C "$upstream" config --add receive.procReceiveRefs refs/for
3+
'
4+
15
test_expect_success "setup proc-receive hook" '
26
write_script "$upstream/hooks/proc-receive" <<-EOF
37
printf >&2 "# proc-receive hook\n"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
test_expect_success "add two receive.procReceiveRefs settings" '
2+
(
3+
cd "$upstream" &&
4+
git config --add receive.procReceiveRefs refs/for &&
5+
git config --add receive.procReceiveRefs refs/review/
6+
)
7+
'

t/t5411/test-0040-process-all-refs.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL)" '
2+
git -C "$upstream" config --unset-all receive.procReceiveRefs &&
3+
git -C "$upstream" config --add receive.procReceiveRefs refs
4+
'
5+
6+
# Refs of upstream : master(A)
7+
# Refs of workbench: master(A) tags/v123
8+
test_expect_success "setup upstream branches ($PROTOCOL)" '
9+
(
10+
cd "$upstream" &&
11+
git update-ref refs/heads/master $B &&
12+
git update-ref refs/heads/foo $A &&
13+
git update-ref refs/heads/bar $A &&
14+
git update-ref refs/heads/baz $A
15+
)
16+
17+
'
18+
19+
test_expect_success "setup proc-receive hook ($PROTOCOL)" '
20+
write_script "$upstream/hooks/proc-receive" <<-EOF
21+
printf >&2 "# proc-receive hook\n"
22+
test-tool proc-receive -v \
23+
-r "ok refs/heads/master" \
24+
-r "option fall-through" \
25+
-r "ok refs/heads/foo" \
26+
-r "option fall-through" \
27+
-r "ok refs/heads/bar" \
28+
-r "option fall-through" \
29+
-r "ok refs/for/master/topic" \
30+
-r "option refname refs/pull/123/head" \
31+
-r "option old-oid $A" \
32+
-r "option new-oid $B" \
33+
-r "ok refs/for/next/topic" \
34+
-r "option refname refs/pull/124/head" \
35+
-r "option old-oid $B" \
36+
-r "option new-oid $A" \
37+
-r "option forced-update"
38+
EOF
39+
'
40+
41+
# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
42+
# Refs of workbench: master(A) tags/v123
43+
# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A)
44+
test_expect_success "proc-receive: process all refs ($PROTOCOL)" '
45+
git -C workbench push -f origin \
46+
HEAD:refs/heads/master \
47+
:refs/heads/foo \
48+
$B:refs/heads/bar \
49+
HEAD:refs/for/master/topic \
50+
HEAD:refs/for/next/topic \
51+
>out 2>&1 &&
52+
make_user_friendly_and_stable_output <out >actual &&
53+
cat >expect <<-EOF &&
54+
remote: # pre-receive hook
55+
remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
56+
remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
57+
remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
58+
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
59+
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
60+
remote: # proc-receive hook
61+
remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
62+
remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
63+
remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
64+
remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
65+
remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
66+
remote: proc-receive> ok refs/heads/master
67+
remote: proc-receive> option fall-through
68+
remote: proc-receive> ok refs/heads/foo
69+
remote: proc-receive> option fall-through
70+
remote: proc-receive> ok refs/heads/bar
71+
remote: proc-receive> option fall-through
72+
remote: proc-receive> ok refs/for/master/topic
73+
remote: proc-receive> option refname refs/pull/123/head
74+
remote: proc-receive> option old-oid <COMMIT-A>
75+
remote: proc-receive> option new-oid <COMMIT-B>
76+
remote: proc-receive> ok refs/for/next/topic
77+
remote: proc-receive> option refname refs/pull/124/head
78+
remote: proc-receive> option old-oid <COMMIT-B>
79+
remote: proc-receive> option new-oid <COMMIT-A>
80+
remote: proc-receive> option forced-update
81+
remote: # post-receive hook
82+
remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
83+
remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
84+
remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
85+
remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
86+
remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
87+
To <URL/of/upstream.git>
88+
<OID-A>..<OID-B> <COMMIT-B> -> bar
89+
- [deleted] foo
90+
+ <OID-B>...<OID-A> HEAD -> master (forced update)
91+
<OID-A>..<OID-B> HEAD -> refs/pull/123/head
92+
+ <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
93+
EOF
94+
test_cmp expect actual &&
95+
git -C "$upstream" show-ref >out &&
96+
make_user_friendly_and_stable_output <out >actual &&
97+
cat >expect <<-EOF &&
98+
<COMMIT-B> refs/heads/bar
99+
<COMMIT-A> refs/heads/baz
100+
<COMMIT-A> refs/heads/master
101+
EOF
102+
test_cmp expect actual
103+
'
104+
105+
# Refs of upstream : master(A) bar(A) baz(B)
106+
# Refs of workbench: master(A) tags/v123
107+
test_expect_success "cleanup ($PROTOCOL)" '
108+
(
109+
cd "$upstream" &&
110+
git update-ref -d refs/heads/bar &&
111+
git update-ref -d refs/heads/baz
112+
)
113+
'

0 commit comments

Comments
 (0)