Skip to content

Commit 15d3af5

Browse files
jiangxingitster
authored andcommitted
receive-pack: add new proc-receive hook
Git calls an internal `execute_commands` function to handle commands sent from client to `git-receive-pack`. Regardless of what references the user pushes, git creates or updates the corresponding references if the user has write-permission. A contributor who has no write-permission, cannot push to the repository directly. So, the contributor has to write commits to an alternate location, and sends pull request by emails or by other ways. We call this workflow as a distributed workflow. It would be more convenient to work in a centralized workflow like what Gerrit provided for some cases. For example, a read-only user who cannot push to a branch directly can run the following `git push` command to push commits to a pseudo reference (has a prefix "refs/for/", not "refs/heads/") to create a code review. git push origin \ HEAD:refs/for/<branch-name>/<session> The `<branch-name>` in the above example can be as simple as "master", or a more complicated branch name like "foo/bar". The `<session>` in the above example command can be the local branch name of the client side, such as "my/topic". We cannot implement a centralized workflow elegantly by using "pre-receive" + "post-receive", because Git will call the internal function "execute_commands" to create references (even the special pseudo reference) between these two hooks. Even though we can delete the temporarily created pseudo reference via the "post-receive" hook, having a temporary reference is not safe for concurrent pushes. So, add a filter and a new handler to support this kind of workflow. The filter will check the prefix of the reference name, and if the command has a special reference name, the filter will turn a specific field (`run_proc_receive`) on for the command. Commands with this filed turned on will be executed by a new handler (a hook named "proc-receive") instead of the internal `execute_commands` function. We can use this "proc-receive" command to create pull requests or send emails for code review. Suggested by Junio, this "proc-receive" hook reads the commands, push-options (optional), and send result using a protocol in pkt-line format. In the following example, the letter "S" stands for "receive-pack" and letter "H" stands for the hook. # Version and features negotiation. S: PKT-LINE(version=1\0push-options atomic...) S: flush-pkt H: PKT-LINE(version=1\0push-options...) H: flush-pkt # Send commands from server to the hook. S: PKT-LINE(<old-oid> <new-oid> <ref>) S: ... ... S: flush-pkt # Send push-options only if the 'push-options' feature is enabled. S: PKT-LINE(push-option) S: ... ... S: flush-pkt # Receive result from the hook. # OK, run this command successfully. H: PKT-LINE(ok <ref>) # NO, I reject it. H: PKT-LINE(ng <ref> <reason>) # Fall through, let 'receive-pack' to execute it. H: PKT-LINE(ok <ref>) H: PKT-LINE(option fall-through) # OK, but has an alternate reference. The alternate reference name # and other status can be given in options H: PKT-LINE(ok <ref>) H: PKT-LINE(option refname <refname>) H: PKT-LINE(option old-oid <old-oid>) H: PKT-LINE(option new-oid <new-oid>) H: PKT-LINE(option forced-update) H: ... ... H: flush-pkt After receiving a command, the hook will execute the command, and may create/update different reference. For example, a command for a pseudo reference "refs/for/master/topic" may create/update different reference such as "refs/pull/123/head". The alternate reference name and other status are given in option lines. The list of commands returned from "proc-receive" will replace the relevant commands that are sent from user to "receive-pack", and "receive-pack" will continue to run the "execute_commands" function and other routines. Finally, the result of the execution of these commands will be reported to end user. The reporting function from "receive-pack" to "send-pack" will be extended in latter commit just like what the "proc-receive" hook reports to "receive-pack". Signed-off-by: Jiang Xin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 38b9197 commit 15d3af5

28 files changed

+2626
-3
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
722722
TEST_BUILTINS_OBJS += test-path-utils.o
723723
TEST_BUILTINS_OBJS += test-pkt-line.o
724724
TEST_BUILTINS_OBJS += test-prio-queue.o
725+
TEST_BUILTINS_OBJS += test-proc-receive.o
725726
TEST_BUILTINS_OBJS += test-progress.o
726727
TEST_BUILTINS_OBJS += test-reach.o
727728
TEST_BUILTINS_OBJS += test-read-cache.o

builtin/receive-pack.c

Lines changed: 293 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,15 @@ static void write_head_info(void)
308308
packet_flush(1);
309309
}
310310

311+
#define RUN_PROC_RECEIVE_SCHEDULED 1
312+
#define RUN_PROC_RECEIVE_RETURNED 2
311313
struct command {
312314
struct command *next;
313315
const char *error_string;
316+
struct ref_push_report *report;
314317
unsigned int skip_update:1,
315-
did_not_exist:1;
318+
did_not_exist:1,
319+
run_proc_receive:2;
316320
int index;
317321
struct object_id old_oid;
318322
struct object_id new_oid;
@@ -838,6 +842,268 @@ static int run_update_hook(struct command *cmd)
838842
return finish_command(&proc);
839843
}
840844

845+
static struct command *find_command_by_refname(struct command *list,
846+
const char *refname)
847+
{
848+
for (; list; list = list->next)
849+
if (!strcmp(list->ref_name, refname))
850+
return list;
851+
return NULL;
852+
}
853+
854+
static int read_proc_receive_report(struct packet_reader *reader,
855+
struct command *commands,
856+
struct strbuf *errmsg)
857+
{
858+
struct command *cmd;
859+
struct command *hint = NULL;
860+
struct ref_push_report *report = NULL;
861+
int new_report = 0;
862+
int code = 0;
863+
int once = 0;
864+
865+
for (;;) {
866+
struct object_id old_oid, new_oid;
867+
const char *head;
868+
const char *refname;
869+
char *p;
870+
871+
if (packet_reader_read(reader) != PACKET_READ_NORMAL)
872+
break;
873+
874+
head = reader->line;
875+
p = strchr(head, ' ');
876+
if (!p) {
877+
strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head);
878+
code = -1;
879+
continue;
880+
}
881+
*p++ = '\0';
882+
if (!strcmp(head, "option")) {
883+
const char *key, *val;
884+
885+
if (!hint || !(report || new_report)) {
886+
if (!once++)
887+
strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n");
888+
code = -1;
889+
continue;
890+
}
891+
if (new_report) {
892+
if (!hint->report) {
893+
hint->report = xcalloc(1, sizeof(struct ref_push_report));
894+
report = hint->report;
895+
} else {
896+
report = hint->report;
897+
while (report->next)
898+
report = report->next;
899+
report->next = xcalloc(1, sizeof(struct ref_push_report));
900+
report = report->next;
901+
}
902+
new_report = 0;
903+
}
904+
key = p;
905+
p = strchr(key, ' ');
906+
if (p)
907+
*p++ = '\0';
908+
val = p;
909+
if (!strcmp(key, "refname"))
910+
report->ref_name = xstrdup_or_null(val);
911+
else if (!strcmp(key, "old-oid") && val &&
912+
!parse_oid_hex(val, &old_oid, &val))
913+
report->old_oid = oiddup(&old_oid);
914+
else if (!strcmp(key, "new-oid") && val &&
915+
!parse_oid_hex(val, &new_oid, &val))
916+
report->new_oid = oiddup(&new_oid);
917+
else if (!strcmp(key, "forced-update"))
918+
report->forced_update = 1;
919+
else if (!strcmp(key, "fall-through"))
920+
/* Fall through, let 'receive-pack' to execute it. */
921+
hint->run_proc_receive = 0;
922+
continue;
923+
}
924+
925+
report = NULL;
926+
new_report = 0;
927+
refname = p;
928+
p = strchr(refname, ' ');
929+
if (p)
930+
*p++ = '\0';
931+
if (strcmp(head, "ok") && strcmp(head, "ng")) {
932+
strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n",
933+
head, refname);
934+
code = -1;
935+
continue;
936+
}
937+
938+
/* first try searching at our hint, falling back to all refs */
939+
if (hint)
940+
hint = find_command_by_refname(hint, refname);
941+
if (!hint)
942+
hint = find_command_by_refname(commands, refname);
943+
if (!hint) {
944+
strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n",
945+
refname);
946+
code = -1;
947+
continue;
948+
}
949+
if (!hint->run_proc_receive) {
950+
strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n",
951+
refname);
952+
code = -1;
953+
continue;
954+
}
955+
hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
956+
if (!strcmp(head, "ng")) {
957+
if (p)
958+
hint->error_string = xstrdup(p);
959+
else
960+
hint->error_string = "failed";
961+
code = -1;
962+
continue;
963+
}
964+
new_report = 1;
965+
}
966+
967+
for (cmd = commands; cmd; cmd = cmd->next)
968+
if (cmd->run_proc_receive && !cmd->error_string &&
969+
!(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {
970+
cmd->error_string = "proc-receive failed to report status";
971+
code = -1;
972+
}
973+
return code;
974+
}
975+
976+
static int run_proc_receive_hook(struct command *commands,
977+
const struct string_list *push_options)
978+
{
979+
struct child_process proc = CHILD_PROCESS_INIT;
980+
struct async muxer;
981+
struct command *cmd;
982+
const char *argv[2];
983+
struct packet_reader reader;
984+
struct strbuf cap = STRBUF_INIT;
985+
struct strbuf errmsg = STRBUF_INIT;
986+
int hook_use_push_options = 0;
987+
int version = 0;
988+
int code;
989+
990+
argv[0] = find_hook("proc-receive");
991+
if (!argv[0]) {
992+
rp_error("cannot find hook 'proc-receive'");
993+
return -1;
994+
}
995+
argv[1] = NULL;
996+
997+
proc.argv = argv;
998+
proc.in = -1;
999+
proc.out = -1;
1000+
proc.trace2_hook_name = "proc-receive";
1001+
1002+
if (use_sideband) {
1003+
memset(&muxer, 0, sizeof(muxer));
1004+
muxer.proc = copy_to_sideband;
1005+
muxer.in = -1;
1006+
code = start_async(&muxer);
1007+
if (code)
1008+
return code;
1009+
proc.err = muxer.in;
1010+
} else {
1011+
proc.err = 0;
1012+
}
1013+
1014+
code = start_command(&proc);
1015+
if (code) {
1016+
if (use_sideband)
1017+
finish_async(&muxer);
1018+
return code;
1019+
}
1020+
1021+
sigchain_push(SIGPIPE, SIG_IGN);
1022+
1023+
/* Version negotiaton */
1024+
packet_reader_init(&reader, proc.out, NULL, 0,
1025+
PACKET_READ_CHOMP_NEWLINE |
1026+
PACKET_READ_GENTLE_ON_EOF);
1027+
if (use_atomic)
1028+
strbuf_addstr(&cap, " atomic");
1029+
if (use_push_options)
1030+
strbuf_addstr(&cap, " push-options");
1031+
if (cap.len) {
1032+
packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1);
1033+
strbuf_release(&cap);
1034+
} else {
1035+
packet_write_fmt(proc.in, "version=1\n");
1036+
}
1037+
packet_flush(proc.in);
1038+
1039+
for (;;) {
1040+
int linelen;
1041+
1042+
if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
1043+
break;
1044+
1045+
if (reader.pktlen > 8 && starts_with(reader.line, "version=")) {
1046+
version = atoi(reader.line + 8);
1047+
linelen = strlen(reader.line);
1048+
if (linelen < reader.pktlen) {
1049+
const char *feature_list = reader.line + linelen + 1;
1050+
if (parse_feature_request(feature_list, "push-options"))
1051+
hook_use_push_options = 1;
1052+
}
1053+
}
1054+
}
1055+
1056+
if (version != 1) {
1057+
strbuf_addf(&errmsg, "proc-receive version '%d' is not supported",
1058+
version);
1059+
code = -1;
1060+
goto cleanup;
1061+
}
1062+
1063+
/* Send commands */
1064+
for (cmd = commands; cmd; cmd = cmd->next) {
1065+
if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)
1066+
continue;
1067+
packet_write_fmt(proc.in, "%s %s %s",
1068+
oid_to_hex(&cmd->old_oid),
1069+
oid_to_hex(&cmd->new_oid),
1070+
cmd->ref_name);
1071+
}
1072+
packet_flush(proc.in);
1073+
1074+
/* Send push options */
1075+
if (hook_use_push_options) {
1076+
struct string_list_item *item;
1077+
1078+
for_each_string_list_item(item, push_options)
1079+
packet_write_fmt(proc.in, "%s", item->string);
1080+
packet_flush(proc.in);
1081+
}
1082+
1083+
/* Read result from proc-receive */
1084+
code = read_proc_receive_report(&reader, commands, &errmsg);
1085+
1086+
cleanup:
1087+
close(proc.in);
1088+
close(proc.out);
1089+
if (use_sideband)
1090+
finish_async(&muxer);
1091+
if (finish_command(&proc))
1092+
code = -1;
1093+
if (errmsg.len >0) {
1094+
char *p = errmsg.buf;
1095+
1096+
p += errmsg.len - 1;
1097+
if (*p == '\n')
1098+
*p = '\0';
1099+
rp_error("%s", errmsg.buf);
1100+
strbuf_release(&errmsg);
1101+
}
1102+
sigchain_pop(SIGPIPE);
1103+
1104+
return code;
1105+
}
1106+
8411107
static char *refuse_unconfigured_deny_msg =
8421108
N_("By default, updating the current branch in a non-bare repository\n"
8431109
"is denied, because it will make the index and work tree inconsistent\n"
@@ -1413,7 +1679,7 @@ static void execute_commands_non_atomic(struct command *commands,
14131679
struct strbuf err = STRBUF_INIT;
14141680

14151681
for (cmd = commands; cmd; cmd = cmd->next) {
1416-
if (!should_process_cmd(cmd))
1682+
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
14171683
continue;
14181684

14191685
transaction = ref_transaction_begin(&err);
@@ -1453,7 +1719,7 @@ static void execute_commands_atomic(struct command *commands,
14531719
}
14541720

14551721
for (cmd = commands; cmd; cmd = cmd->next) {
1456-
if (!should_process_cmd(cmd))
1722+
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
14571723
continue;
14581724

14591725
cmd->error_string = update(cmd, si);
@@ -1489,6 +1755,7 @@ static void execute_commands(struct command *commands,
14891755
struct iterate_data data;
14901756
struct async muxer;
14911757
int err_fd = 0;
1758+
int run_proc_receive = 0;
14921759

14931760
if (unpacker_error) {
14941761
for (cmd = commands; cmd; cmd = cmd->next)
@@ -1518,6 +1785,21 @@ static void execute_commands(struct command *commands,
15181785

15191786
reject_updates_to_hidden(commands);
15201787

1788+
/*
1789+
* Try to find commands that have special prefix in their reference names,
1790+
* and mark them to run an external "proc-receive" hook later.
1791+
*/
1792+
for (cmd = commands; cmd; cmd = cmd->next) {
1793+
if (!should_process_cmd(cmd))
1794+
continue;
1795+
1796+
/* TODO: replace the fixed prefix by looking up git config variables. */
1797+
if (!strncmp(cmd->ref_name, "refs/for/", 9)) {
1798+
cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
1799+
run_proc_receive = 1;
1800+
}
1801+
}
1802+
15211803
if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
15221804
for (cmd = commands; cmd; cmd = cmd->next) {
15231805
if (!cmd->error_string)
@@ -1544,6 +1826,14 @@ static void execute_commands(struct command *commands,
15441826
free(head_name_to_free);
15451827
head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
15461828

1829+
if (run_proc_receive &&
1830+
run_proc_receive_hook(commands, push_options))
1831+
for (cmd = commands; cmd; cmd = cmd->next)
1832+
if (!cmd->error_string &&
1833+
!(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&
1834+
(cmd->run_proc_receive || use_atomic))
1835+
cmd->error_string = "fail to run proc-receive hook";
1836+
15471837
if (use_atomic)
15481838
execute_commands_atomic(commands, si);
15491839
else

remote.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ int for_each_remote(each_remote_fn fn, void *priv);
9393

9494
int remote_has_url(struct remote *remote, const char *url);
9595

96+
struct ref_push_report {
97+
const char *ref_name;
98+
struct object_id *old_oid;
99+
struct object_id *new_oid;
100+
unsigned int forced_update:1;
101+
struct ref_push_report *next;
102+
};
103+
96104
struct ref {
97105
struct ref *next;
98106
struct object_id old_oid;

0 commit comments

Comments
 (0)