Git fork
at reftables-rust 211 lines 4.8 kB view raw
1#include "builtin.h" 2#include "transport.h" 3#include "run-command.h" 4#include "pkt-line.h" 5 6static const char usage_msg[] = 7 "git remote-ext <remote> <url>"; 8 9/* 10 * URL syntax: 11 * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. 12 * Special characters: 13 * '% ': Literal space in argument. 14 * '%%': Literal percent sign. 15 * '%S': Name of service (git-upload-pack/git-upload-archive/ 16 * git-receive-pack. 17 * '%s': Same as \s, but with possible git- prefix stripped. 18 * '%G': Only allowed as first 'character' of argument. Do not pass this 19 * Argument to command, instead send this as name of repository 20 * in in-line git://-style request (also activates sending this 21 * style of request). 22 * '%V': Only allowed as first 'character' of argument. Used in 23 * conjunction with '%G': Do not pass this argument to command, 24 * instead send this as vhost in git://-style request (note: does 25 * not activate sending git:// style request). 26 */ 27 28static char *git_req; 29static char *git_req_vhost; 30 31static char *strip_escapes(const char *str, const char *service, 32 const char **next) 33{ 34 size_t rpos = 0; 35 int escape = 0; 36 char special = 0; 37 const char *service_noprefix = service; 38 struct strbuf ret = STRBUF_INIT; 39 40 skip_prefix(service_noprefix, "git-", &service_noprefix); 41 42 /* Pass the service to command. */ 43 setenv("GIT_EXT_SERVICE", service, 1); 44 setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1); 45 46 /* Scan the length of argument. */ 47 while (str[rpos] && (escape || str[rpos] != ' ')) { 48 if (escape) { 49 switch (str[rpos]) { 50 case ' ': 51 case '%': 52 case 's': 53 case 'S': 54 break; 55 case 'G': 56 case 'V': 57 special = str[rpos]; 58 if (rpos == 1) 59 break; 60 /* fallthrough */ 61 default: 62 die("Bad remote-ext placeholder '%%%c'.", 63 str[rpos]); 64 } 65 escape = 0; 66 } else 67 escape = (str[rpos] == '%'); 68 rpos++; 69 } 70 if (escape && !str[rpos]) 71 die("remote-ext command has incomplete placeholder"); 72 *next = str + rpos; 73 if (**next == ' ') 74 ++*next; /* Skip over space */ 75 76 /* 77 * Do the actual placeholder substitution. The string will be short 78 * enough not to overflow integers. 79 */ 80 rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */ 81 escape = 0; 82 while (str[rpos] && (escape || str[rpos] != ' ')) { 83 if (escape) { 84 switch (str[rpos]) { 85 case ' ': 86 case '%': 87 strbuf_addch(&ret, str[rpos]); 88 break; 89 case 's': 90 strbuf_addstr(&ret, service_noprefix); 91 break; 92 case 'S': 93 strbuf_addstr(&ret, service); 94 break; 95 } 96 escape = 0; 97 } else 98 switch (str[rpos]) { 99 case '%': 100 escape = 1; 101 break; 102 default: 103 strbuf_addch(&ret, str[rpos]); 104 break; 105 } 106 rpos++; 107 } 108 switch (special) { 109 case 'G': 110 git_req = strbuf_detach(&ret, NULL); 111 return NULL; 112 case 'V': 113 git_req_vhost = strbuf_detach(&ret, NULL); 114 return NULL; 115 default: 116 return strbuf_detach(&ret, NULL); 117 } 118} 119 120static void parse_argv(struct strvec *out, const char *arg, const char *service) 121{ 122 while (*arg) { 123 char *expanded = strip_escapes(arg, service, &arg); 124 if (expanded) 125 strvec_push(out, expanded); 126 free(expanded); 127 } 128} 129 130static void send_git_request(int stdin_fd, const char *serv, const char *repo, 131 const char *vhost) 132{ 133 if (!vhost) 134 packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0); 135 else 136 packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, 137 vhost, 0); 138} 139 140static int run_child(const char *arg, const char *service) 141{ 142 int r; 143 struct child_process child = CHILD_PROCESS_INIT; 144 145 child.in = -1; 146 child.out = -1; 147 child.err = 0; 148 parse_argv(&child.args, arg, service); 149 150 if (start_command(&child) < 0) 151 die("Can't run specified command"); 152 153 if (git_req) 154 send_git_request(child.in, service, git_req, git_req_vhost); 155 156 r = bidirectional_transfer_loop(child.out, child.in); 157 if (!r) 158 r = finish_command(&child); 159 else 160 finish_command(&child); 161 return r; 162} 163 164#define MAXCOMMAND 4096 165 166static int command_loop(const char *child) 167{ 168 char buffer[MAXCOMMAND]; 169 170 while (1) { 171 size_t i; 172 const char *arg; 173 174 if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { 175 if (ferror(stdin)) 176 die("Command input error"); 177 exit(0); 178 } 179 /* Strip end of line characters. */ 180 i = strlen(buffer); 181 while (i > 0 && isspace(buffer[i - 1])) 182 buffer[--i] = 0; 183 184 if (!strcmp(buffer, "capabilities")) { 185 printf("*connect\n\n"); 186 fflush(stdout); 187 } else if (skip_prefix(buffer, "connect ", &arg)) { 188 printf("\n"); 189 fflush(stdout); 190 return run_child(child, arg); 191 } else { 192 fprintf(stderr, "Bad command"); 193 return 1; 194 } 195 } 196} 197 198int cmd_remote_ext(int argc, 199 const char **argv, 200 const char *prefix, 201 struct repository *repo UNUSED) 202{ 203 BUG_ON_NON_EMPTY_PREFIX(prefix); 204 205 show_usage_if_asked(argc, argv, usage_msg); 206 207 if (argc != 3) 208 usage(usage_msg); 209 210 return command_loop(argv[2]); 211}