builtin-push: resurrect parsing of Push: lines
[git.git] / builtin-push.c
1 /*
2  * "git push"
3  */
4 #include "cache.h"
5 #include "refs.h"
6 #include "run-command.h"
7 #include "builtin.h"
8
9 #define MAX_URI (16)
10
11 static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]";
12
13 static int all = 0, tags = 0, force = 0, thin = 1;
14 static const char *execute = NULL;
15
16 #define BUF_SIZE (2084)
17 static char buffer[BUF_SIZE];
18
19 static const char **refspec = NULL;
20 static int refspec_nr = 0;
21
22 static void add_refspec(const char *ref)
23 {
24         int nr = refspec_nr + 1;
25         refspec = xrealloc(refspec, nr * sizeof(char *));
26         refspec[nr-1] = ref;
27         refspec_nr = nr;
28 }
29
30 static int expand_one_ref(const char *ref, const unsigned char *sha1)
31 {
32         /* Ignore the "refs/" at the beginning of the refname */
33         ref += 5;
34
35         if (strncmp(ref, "tags/", 5))
36                 return 0;
37
38         add_refspec(strdup(ref));
39         return 0;
40 }
41
42 static void expand_refspecs(void)
43 {
44         if (all) {
45                 if (refspec_nr)
46                         die("cannot mix '--all' and a refspec");
47
48                 /*
49                  * No need to expand "--all" - we'll just use
50                  * the "--all" flag to send-pack
51                  */
52                 return;
53         }
54         if (!tags)
55                 return;
56         for_each_ref(expand_one_ref);
57 }
58
59 static void set_refspecs(const char **refs, int nr)
60 {
61         if (nr) {
62                 size_t bytes = nr * sizeof(char *);
63
64                 refspec = xrealloc(refspec, bytes);
65                 memcpy(refspec, refs, bytes);
66                 refspec_nr = nr;
67         }
68         expand_refspecs();
69 }
70
71 #define MAX_REFSPECS 10
72 static int current_refspec = 0;
73 static char *refspecs_[MAX_REFSPECS];
74
75 static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
76 {
77         int n = 0;
78         FILE *f = fopen(git_path("remotes/%s", repo), "r");
79
80         if (!f)
81                 return -1;
82         while (fgets(buffer, BUF_SIZE, f)) {
83                 int is_refspec;
84                 char *s, *p;
85
86                 if (!strncmp("URL: ", buffer, 5)) {
87                         is_refspec = 0;
88                         s = buffer + 5;
89                 } else if (!strncmp("Push: ", buffer, 6)) {
90                         is_refspec = 1;
91                         s = buffer + 6;
92                 } else
93                         continue;
94
95                 /* Remove whitespace at the head.. */
96                 while (isspace(*s))
97                         s++;
98                 if (!*s)
99                         continue;
100
101                 /* ..and at the end */
102                 p = s + strlen(s);
103                 while (isspace(p[-1]))
104                         *--p = 0;
105
106                 if (!is_refspec && n < MAX_URI)
107                         uri[n++] = strdup(s);
108                 else if (is_refspec && current_refspec < MAX_REFSPECS)
109                         refspecs_[current_refspec++] = strdup(s);
110         }
111         fclose(f);
112         if (!n)
113                 die("remote '%s' has no URL", repo);
114         return n;
115 }
116
117 static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
118 {
119         const char *slash = strchr(repo, '/');
120         int n = slash ? slash - repo : 1000;
121         FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
122         char *s, *p;
123         int len;
124
125         if (!f)
126                 return 0;
127         s = fgets(buffer, BUF_SIZE, f);
128         fclose(f);
129         if (!s)
130                 return 0;
131         while (isspace(*s))
132                 s++;
133         if (!*s)
134                 return 0;
135         p = s + strlen(s);
136         while (isspace(p[-1]))
137                 *--p = 0;
138         len = p - s;
139         if (slash)
140                 len += strlen(slash);
141         p = xmalloc(len + 1);
142         strcpy(p, s);
143         if (slash)
144                 strcat(p, slash);
145         uri[0] = p;
146         return 1;
147 }
148
149 static int get_uri(const char *repo, const char *uri[MAX_URI])
150 {
151         int n;
152
153         if (*repo != '/') {
154                 current_refspec = 0;
155
156                 n = get_remotes_uri(repo, uri);
157                 if (n > 0)
158                         return n;
159
160                 n = get_branches_uri(repo, uri);
161                 if (n > 0)
162                         return n;
163         }
164
165         uri[0] = repo;
166         return 1;
167 }
168
169 static int do_push(const char *repo)
170 {
171         const char *uri[MAX_URI];
172         int i, n = get_uri(repo, uri);
173         int remote;
174         const char **argv;
175         int argc;
176
177         n = get_uri(repo, uri);
178         if (n <= 0)
179                 die("bad repository '%s'", repo);
180
181         if (refspec_nr == 0)
182                 set_refspecs((const char**)refspecs_, current_refspec);
183
184         argv = xmalloc((refspec_nr + 10) * sizeof(char *));
185         argv[0] = "dummy-send-pack";
186         argc = 1;
187         if (all)
188                 argv[argc++] = "--all";
189         if (force)
190                 argv[argc++] = "--force";
191         if (execute)
192                 argv[argc++] = execute;
193         if (thin)
194                 argv[argc++] = "--thin";
195         remote = argc;
196         argv[argc++] = "dummy-remote";
197         while (refspec_nr--)
198                 argv[argc++] = *refspec++;
199         argv[argc] = NULL;
200
201         for (i = 0; i < n; i++) {
202                 int error;
203                 const char *dest = uri[i];
204                 const char *sender = "git-send-pack";
205                 if (!strncmp(dest, "http://", 7) ||
206                     !strncmp(dest, "https://", 8))
207                         sender = "git-http-push";
208                 argv[0] = sender;
209                 argv[remote] = dest;
210                 error = run_command_v(argc, argv);
211                 if (!error)
212                         continue;
213                 switch (error) {
214                 case -ERR_RUN_COMMAND_FORK:
215                         die("unable to fork for %s", sender);
216                 case -ERR_RUN_COMMAND_EXEC:
217                         die("unable to exec %s", sender);
218                 case -ERR_RUN_COMMAND_WAITPID:
219                 case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
220                 case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
221                 case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
222                         die("%s died with strange error", sender);
223                 default:
224                         return -error;
225                 }
226         }
227         return 0;
228 }
229
230 int cmd_push(int argc, const char **argv, char **envp)
231 {
232         int i;
233         const char *repo = "origin";    // default repository
234
235         for (i = 1; i < argc; i++) {
236                 const char *arg = argv[i];
237
238                 if (arg[0] != '-') {
239                         repo = arg;
240                         i++;
241                         break;
242                 }
243                 if (!strcmp(arg, "--all")) {
244                         all = 1;
245                         continue;
246                 }
247                 if (!strcmp(arg, "--tags")) {
248                         tags = 1;
249                         continue;
250                 }
251                 if (!strcmp(arg, "--force")) {
252                         force = 1;
253                         continue;
254                 }
255                 if (!strcmp(arg, "--thin")) {
256                         thin = 1;
257                         continue;
258                 }
259                 if (!strcmp(arg, "--no-thin")) {
260                         thin = 0;
261                         continue;
262                 }
263                 if (!strncmp(arg, "--exec=", 7)) {
264                         execute = arg;
265                         continue;
266                 }
267                 usage(push_usage);
268         }
269         set_refspecs(argv + i, argc - i);
270         return do_push(repo);
271 }