Make git-remote a builtin
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Johannes Schindelin committed
Feb 29, 2008 at 01:45 UTC
211c89682eeef310f39022b91e88d07cd5784953
8 files changed
+556
-4
Makefile
+2
-1
@@ -243,7 +243,7 @@ SCRIPT_SH = \
243
SCRIPT_PERL = \
244
git-add--interactive.perl \
245
git-archimport.perl git-cvsimport.perl git-relink.perl \
246
- git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \
246
+ git-cvsserver.perl git-cvsexportcommit.perl \
247
git-send-email.perl git-svn.perl
248
249
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
@@ -380,6 +380,7 @@ BUILTIN_OBJS = \
380
builtin-push.o \
381
builtin-read-tree.o \
382
builtin-reflog.o \
383
+ builtin-remote.o \
384
builtin-send-pack.o \
385
builtin-config.o \
386
builtin-rerere.o \
builtin-remote.c
new
+547
@@ -0,0 +1,547 @@
1
+#include "cache.h"
2
+#include "parse-options.h"
3
+#include "transport.h"
4
+#include "remote.h"
5
+#include "path-list.h"
6
+#include "strbuf.h"
7
+#include "run-command.h"
8
+#include "refs.h"
9
+
10
+static const char * const builtin_remote_usage[] = {
11
+ "git remote",
12
+ "git remote add <name> <url>",
13
+ "git remote rm <name>",
14
+ "git remote show <name>",
15
+ "git remote prune <name>",
16
+ "git remote update [group]",
17
+ NULL
18
+};
19
+
20
+static int verbose;
21
+
22
+static inline int postfixcmp(const char *string, const char *postfix)
23
+{
24
+ int len1 = strlen(string), len2 = strlen(postfix);
25
+ if (len1 < len2)
26
+ return 1;
27
+ return strcmp(string + len1 - len2, postfix);
28
+}
29
+
30
+static inline const char *skip_prefix(const char *name, const char *prefix)
31
+{
32
+ return !name ? "" :
33
+ prefixcmp(name, prefix) ? name : name + strlen(prefix);
34
+}
35
+
36
+static int opt_parse_track(const struct option *opt, const char *arg, int not)
37
+{
38
+ struct path_list *list = opt->value;
39
+ if (not)
40
+ path_list_clear(list, 0);
41
+ else
42
+ path_list_append(arg, list);
43
+ return 0;
44
+}
45
+
46
+static int fetch_remote(const char *name)
47
+{
48
+ const char *argv[] = { "fetch", name, NULL };
49
+ if (run_command_v_opt(argv, RUN_GIT_CMD))
50
+ return error("Could not fetch %s", name);
51
+ return 0;
52
+}
53
+
54
+static int add(int argc, const char **argv)
55
+{
56
+ int fetch = 0, mirror = 0;
57
+ struct path_list track = { NULL, 0, 0 };
58
+ const char *master = NULL;
59
+ struct remote *remote;
60
+ struct strbuf buf, buf2;
61
+ const char *name, *url;
62
+ int i;
63
+
64
+ struct option options[] = {
65
+ OPT_GROUP("add specific options"),
66
+ OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
67
+ OPT_CALLBACK('t', "track", &track, "branch",
68
+ "branch(es) to track", opt_parse_track),
69
+ OPT_STRING('m', "master", &master, "branch", "master branch"),
70
+ OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
71
+ OPT_END()
72
+ };
73
+
74
+ argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
75
+
76
+ if (argc < 2)
77
+ usage_with_options(builtin_remote_usage, options);
78
+
79
+ name = argv[0];
80
+ url = argv[1];
81
+
82
+ remote = remote_get(name);
83
+ if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
84
+ remote->fetch_refspec_nr))
85
+ die("remote %s already exists.", name);
86
+
87
+ strbuf_init(&buf, 0);
88
+ strbuf_init(&buf2, 0);
89
+
90
+ strbuf_addf(&buf, "remote.%s.url", name);
91
+ if (git_config_set(buf.buf, url))
92
+ return 1;
93
+
94
+ if (track.nr == 0)
95
+ path_list_append("*", &track);
96
+ for (i = 0; i < track.nr; i++) {
97
+ struct path_list_item *item = track.items + i;
98
+
99
+ strbuf_reset(&buf);
100
+ strbuf_addf(&buf, "remote.%s.fetch", name);
101
+
102
+ strbuf_reset(&buf2);
103
+ if (mirror)
104
+ strbuf_addf(&buf2, "refs/%s:refs/%s",
105
+ item->path, item->path);
106
+ else
107
+ strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
108
+ item->path, name, item->path);
109
+ if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
110
+ return 1;
111
+ }
112
+
113
+ if (fetch && fetch_remote(name))
114
+ return 1;
115
+
116
+ if (master) {
117
+ strbuf_reset(&buf);
118
+ strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
119
+
120
+ strbuf_reset(&buf2);
121
+ strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
122
+
123
+ if (create_symref(buf.buf, buf2.buf, "remote add"))
124
+ return error("Could not setup master '%s'", master);
125
+ }
126
+
127
+ strbuf_release(&buf);
128
+ strbuf_release(&buf2);
129
+ path_list_clear(&track, 0);
130
+
131
+ return 0;
132
+}
133
+
134
+struct branch_info {
135
+ char *remote;
136
+ struct path_list merge;
137
+};
138
+
139
+static struct path_list branch_list;
140
+
141
+static int config_read_branches(const char *key, const char *value)
142
+{
143
+ if (!prefixcmp(key, "branch.")) {
144
+ char *name;
145
+ struct path_list_item *item;
146
+ struct branch_info *info;
147
+ enum { REMOTE, MERGE } type;
148
+
149
+ key += 7;
150
+ if (!postfixcmp(key, ".remote")) {
151
+ name = xstrndup(key, strlen(key) - 7);
152
+ type = REMOTE;
153
+ } else if (!postfixcmp(key, ".merge")) {
154
+ name = xstrndup(key, strlen(key) - 6);
155
+ type = MERGE;
156
+ } else
157
+ return 0;
158
+
159
+ item = path_list_insert(name, &branch_list);
160
+
161
+ if (!item->util)
162
+ item->util = xcalloc(sizeof(struct branch_info), 1);
163
+ info = item->util;
164
+ if (type == REMOTE) {
165
+ if (info->remote)
166
+ warning("more than one branch.%s", key);
167
+ info->remote = xstrdup(value);
168
+ } else {
169
+ char *space = strchr(value, ' ');
170
+ value = skip_prefix(value, "refs/heads/");
171
+ while (space) {
172
+ char *merge;
173
+ merge = xstrndup(value, space - value);
174
+ path_list_append(merge, &info->merge);
175
+ value = skip_prefix(space + 1, "refs/heads/");
176
+ space = strchr(value, ' ');
177
+ }
178
+ path_list_append(xstrdup(value), &info->merge);
179
+ }
180
+ }
181
+ return 0;
182
+}
183
+
184
+static void read_branches(void)
185
+{
186
+ if (branch_list.nr)
187
+ return;
188
+ git_config(config_read_branches);
189
+ sort_path_list(&branch_list);
190
+}
191
+
192
+struct ref_states {
193
+ struct remote *remote;
194
+ struct strbuf remote_prefix;
195
+ struct path_list new, stale, tracked;
196
+};
197
+
198
+static int handle_one_branch(const char *refname,
199
+ const unsigned char *sha1, int flags, void *cb_data)
200
+{
201
+ struct ref_states *states = cb_data;
202
+ struct refspec refspec;
203
+
204
+ memset(&refspec, 0, sizeof(refspec));
205
+ refspec.dst = (char *)refname;
206
+ if (!remote_find_tracking(states->remote, &refspec)) {
207
+ struct path_list_item *item;
208
+ const char *name = skip_prefix(refspec.src, "refs/heads/");
209
+ if (unsorted_path_list_has_path(&states->tracked, name) ||
210
+ unsorted_path_list_has_path(&states->new,
211
+ name))
212
+ return 0;
213
+ item = path_list_append(name, &states->stale);
214
+ item->util = xstrdup(refname);
215
+ }
216
+ return 0;
217
+}
218
+
219
+static int get_ref_states(const struct ref *ref, struct ref_states *states)
220
+{
221
+ struct ref *fetch_map = NULL, **tail = &fetch_map;
222
+ int i;
223
+
224
+ for (i = 0; i < states->remote->fetch_refspec_nr; i++)
225
+ if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1))
226
+ die("Could not get fetch map for refspec %s",
227
+ states->remote->fetch_refspec[i]);
228
+
229
+ states->new.strdup_paths = states->tracked.strdup_paths = 1;
230
+ for (ref = fetch_map; ref; ref = ref->next) {
231
+ struct path_list *target = &states->tracked;
232
+ unsigned char sha1[20];
233
+ void *util = NULL;
234
+
235
+ if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
236
+ target = &states->new;
237
+ else {
238
+ target = &states->tracked;
239
+ if (hashcmp(sha1, ref->new_sha1))
240
+ util = &states;
241
+ }
242
+ path_list_append(skip_prefix(ref->name, "refs/heads/"),
243
+ target)->util = util;
244
+ }
245
+ free_refs(fetch_map);
246
+
247
+ strbuf_addf(&states->remote_prefix,
248
+ "refs/remotes/%s/", states->remote->name);
249
+ for_each_ref(handle_one_branch, states);
250
+ sort_path_list(&states->stale);
251
+
252
+ return 0;
253
+}
254
+
255
+struct branches_for_remote {
256
+ const char *prefix;
257
+ struct path_list *branches;
258
+};
259
+
260
+static int add_branch_for_removal(const char *refname,
261
+ const unsigned char *sha1, int flags, void *cb_data)
262
+{
263
+ struct branches_for_remote *branches = cb_data;
264
+
265
+ if (!prefixcmp(refname, branches->prefix)) {
266
+ struct path_list_item *item;
267
+ item = path_list_append(refname, branches->branches);
268
+ item->util = xmalloc(20);
269
+ hashcpy(item->util, sha1);
270
+ }
271
+
272
+ return 0;
273
+}
274
+
275
+static int remove_branches(struct path_list *branches)
276
+{
277
+ int i, result = 0;
278
+ for (i = 0; i < branches->nr; i++) {
279
+ struct path_list_item *item = branches->items + i;
280
+ const char *refname = item->path;
281
+ unsigned char *sha1 = item->util;
282
+
283
+ if (delete_ref(refname, sha1))
284
+ result |= error("Could not remove branch %s", refname);
285
+ }
286
+ return result;
287
+}
288
+
289
+static int rm(int argc, const char **argv)
290
+{
291
+ struct option options[] = {
292
+ OPT_END()
293
+ };
294
+ struct remote *remote;
295
+ struct strbuf buf;
296
+ struct path_list branches = { NULL, 0, 0, 1 };
297
+ struct branches_for_remote cb_data = { NULL, &branches };
298
+ int i;
299
+
300
+ if (argc != 2)
301
+ usage_with_options(builtin_remote_usage, options);
302
+
303
+ remote = remote_get(argv[1]);
304
+ if (!remote)
305
+ die("No such remote: %s", argv[1]);
306
+
307
+ strbuf_init(&buf, 0);
308
+ strbuf_addf(&buf, "remote.%s", remote->name);
309
+ if (git_config_rename_section(buf.buf, NULL) < 1)
310
+ return error("Could not remove config section '%s'", buf.buf);
311
+
312
+ read_branches();
313
+ for (i = 0; i < branch_list.nr; i++) {
314
+ struct path_list_item *item = branch_list.items + i;
315
+ struct branch_info *info = item->util;
316
+ if (info->remote && !strcmp(info->remote, remote->name)) {
317
+ const char *keys[] = { "remote", "merge", NULL }, **k;
318
+ for (k = keys; *k; k++) {
319
+ strbuf_reset(&buf);
320
+ strbuf_addf(&buf, "branch.%s.%s",
321
+ item->path, *k);
322
+ if (git_config_set(buf.buf, NULL)) {
323
+ strbuf_release(&buf);
324
+ return -1;
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ /*
331
+ * We cannot just pass a function to for_each_ref() which deletes
332
+ * the branches one by one, since for_each_ref() relies on cached
333
+ * refs, which are invalidated when deleting a branch.
334
+ */
335
+ strbuf_reset(&buf);
336
+ strbuf_addf(&buf, "refs/remotes/%s/", remote->name);
337
+ cb_data.prefix = buf.buf;
338
+ i = for_each_ref(add_branch_for_removal, &cb_data);
339
+ strbuf_release(&buf);
340
+
341
+ if (!i)
342
+ i = remove_branches(&branches);
343
+ path_list_clear(&branches, 1);
344
+
345
+ return i;
346
+}
347
+
348
+static void show_list(const char *title, struct path_list *list)
349
+{
350
+ int i;
351
+
352
+ if (!list->nr)
353
+ return;
354
+
355
+ printf(title, list->nr > 1 ? "es" : "");
356
+ printf("\n ");
357
+ for (i = 0; i < list->nr; i++)
358
+ printf("%s%s", i ? " " : "", list->items[i].path);
359
+ printf("\n");
360
+}
361
+
362
+static int show_or_prune(int argc, const char **argv, int prune)
363
+{
364
+ int dry_run = 0, result = 0;
365
+ struct option options[] = {
366
+ OPT_GROUP("show specific options"),
367
+ OPT__DRY_RUN(&dry_run),
368
+ OPT_END()
369
+ };
370
+ struct ref_states states;
371
+
372
+ argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
373
+
374
+ if (argc < 1)
375
+ usage_with_options(builtin_remote_usage, options);
376
+
377
+ memset(&states, 0, sizeof(states));
378
+ for (; argc; argc--, argv++) {
379
+ struct transport *transport;
380
+ const struct ref *ref;
381
+ struct strbuf buf;
382
+ int i, got_states;
383
+
384
+ states.remote = remote_get(*argv);
385
+ if (!states.remote)
386
+ return error("No such remote: %s", *argv);
387
+ transport = transport_get(NULL, states.remote->url_nr > 0 ?
388
+ states.remote->url[0] : NULL);
389
+ ref = transport_get_remote_refs(transport);
390
+
391
+ read_branches();
392
+ got_states = get_ref_states(ref, &states);
393
+ if (got_states)
394
+ result = error("Error getting local info for '%s'",
395
+ states.remote->name);
396
+
397
+ if (prune) {
398
+ struct strbuf buf;
399
+
400
+ strbuf_init(&buf, 0);
401
+ for (i = 0; i < states.stale.nr; i++) {
402
+ strbuf_reset(&buf);
403
+ strbuf_addf(&buf, "refs/remotes/%s/%s", *argv,
404
+ states.stale.items[i].path);
405
+ result |= delete_ref(buf.buf, NULL);
406
+ }
407
+
408
+ strbuf_release(&buf);
409
+ goto cleanup_states;
410
+ }
411
+
412
+ printf("* remote %s\n URL: %s\n", *argv,
413
+ states.remote->url_nr > 0 ?
414
+ states.remote->url[0] : "(no URL)");
415
+
416
+ for (i = 0; i < branch_list.nr; i++) {
417
+ struct path_list_item *branch = branch_list.items + i;
418
+ struct branch_info *info = branch->util;
419
+ int j;
420
+
421
+ if (!info->merge.nr || strcmp(*argv, info->remote))
422
+ continue;
423
+ printf(" Remote branch%s merged with 'git pull' "
424
+ "while on branch %s\n ",
425
+ info->merge.nr > 1 ? "es" : "",
426
+ branch->path);
427
+ for (j = 0; j < info->merge.nr; j++)
428
+ printf(" %s", info->merge.items[j].path);
429
+ printf("\n");
430
+ }
431
+
432
+ if (got_states)
433
+ continue;
434
+ strbuf_init(&buf, 0);
435
+ strbuf_addf(&buf, " New remote branch%%s (next fetch will "
436
+ "store in remotes/%s)", states.remote->name);
437
+ show_list(buf.buf, &states.new);
438
+ strbuf_release(&buf);
439
+ show_list(" Stale tracking branch%s (use 'git remote prune')",
440
+ &states.stale);
441
+ show_list(" Tracked remote branch%s",
442
+ &states.tracked);
443
+
444
+ if (states.remote->push_refspec_nr) {
445
+ printf(" Local branch%s pushed with 'git push'\n ",
446
+ states.remote->push_refspec_nr > 1 ?
447
+ "es" : "");
448
+ for (i = 0; i < states.remote->push_refspec_nr; i++) {
449
+ struct refspec *spec = states.remote->push + i;
450
+ printf(" %s%s%s%s", spec->force ? "+" : "",
451
+ skip_prefix(spec->src, "refs/heads/"),
452
+ spec->dst ? ":" : "",
453
+ skip_prefix(spec->dst, "refs/heads/"));
454
+ }
455
+ }
456
+cleanup_states:
457
+ /* NEEDSWORK: free remote */
458
+ path_list_clear(&states.new, 0);
459
+ path_list_clear(&states.stale, 0);
460
+ path_list_clear(&states.tracked, 0);
461
+ }
462
+
463
+ return result;
464
+}
465
+
466
+static int update_one(struct remote *remote, void *priv)
467
+{
468
+ if (!remote->skip_default_update)
469
+ return fetch_remote(remote->name);
470
+ return 0;
471
+}
472
+
473
+static int update(int argc, const char **argv)
474
+{
475
+ int i;
476
+
477
+ if (argc < 2)
478
+ return for_each_remote(update_one, NULL);
479
+
480
+ for (i = 1; i < argc; i++)
481
+ if (fetch_remote(argv[i]))
482
+ return 1;
483
+ return 0;
484
+}
485
+
486
+static int get_one_entry(struct remote *remote, void *priv)
487
+{
488
+ struct path_list *list = priv;
489
+
490
+ path_list_append(remote->name, list)->util = remote->url_nr ?
491
+ (void *)remote->url[0] : NULL;
492
+ if (remote->url_nr > 1)
493
+ warning("Remote %s has more than one URL", remote->name);
494
+
495
+ return 0;
496
+}
497
+
498
+static int show_all(void)
499
+{
500
+ struct path_list list = { NULL, 0, 0 };
501
+ int result = for_each_remote(get_one_entry, &list);
502
+
503
+ if (!result) {
504
+ int i;
505
+
506
+ sort_path_list(&list);
507
+ for (i = 0; i < list.nr; i++) {
508
+ struct path_list_item *item = list.items + i;
509
+ printf("%s%s%s\n", item->path,
510
+ verbose ? "\t" : "",
511
+ verbose && item->util ?
512
+ (const char *)item->util : "");
513
+ }
514
+ }
515
+ return result;
516
+}
517
+
518
+int cmd_remote(int argc, const char **argv, const char *prefix)
519
+{
520
+ struct option options[] = {
521
+ OPT__VERBOSE(&verbose),
522
+ OPT_END()
523
+ };
524
+ int result;
525
+
526
+ argc = parse_options(argc, argv, options, builtin_remote_usage,
527
+ PARSE_OPT_STOP_AT_NON_OPTION);
528
+
529
+ if (argc < 1)
530
+ result = show_all();
531
+ else if (!strcmp(argv[0], "add"))
532
+ result = add(argc, argv);
533
+ else if (!strcmp(argv[0], "rm"))
534
+ result = rm(argc, argv);
535
+ else if (!strcmp(argv[0], "show"))
536
+ result = show_or_prune(argc, argv, 0);
537
+ else if (!strcmp(argv[0], "prune"))
538
+ result = show_or_prune(argc, argv, 1);
539
+ else if (!strcmp(argv[0], "update"))
540
+ result = update(argc, argv);
541
+ else {
542
+ error("Unknown subcommand: %s", argv[0]);
543
+ usage_with_options(builtin_remote_usage, options);
544
+ }
545
+
546
+ return result ? 1 : 0;
547
+}
builtin.h
+1
@@ -67,6 +67,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
67
extern int cmd_push(int argc, const char **argv, const char *prefix);
68
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
69
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
70
+extern int cmd_remote(int argc, const char **argv, const char *prefix);
71
extern int cmd_config(int argc, const char **argv, const char *prefix);
72
extern int cmd_rerere(int argc, const char **argv, const char *prefix);
73
extern int cmd_reset(int argc, const char **argv, const char *prefix);
contrib/examples/git-remote.perl
renamed
git.c
+1
@@ -334,6 +334,7 @@ static void handle_internal_command(int argc, const char **argv)
334
{ "push", cmd_push, RUN_SETUP },
335
{ "read-tree", cmd_read_tree, RUN_SETUP },
336
{ "reflog", cmd_reflog, RUN_SETUP },
337
+ { "remote", cmd_remote, RUN_SETUP },
338
{ "repo-config", cmd_config },
339
{ "rerere", cmd_rerere, RUN_SETUP },
340
{ "reset", cmd_reset, RUN_SETUP },
remote.c
+2
-1
@@ -357,7 +357,8 @@ static int handle_config(const char *key, const char *value)
357
remote->fetch_tags = -1;
358
} else if (!strcmp(subkey, ".proxy")) {
359
remote->http_proxy = xstrdup(value);
360
- }
360
+ } else if (!strcmp(subkey, ".skipdefaultupdate"))
361
+ remote->skip_default_update = 1;
362
return 0;
363
}
364
remote.h
+1
@@ -25,6 +25,7 @@ struct remote {
25
* 2 to always fetch tags
26
*/
27
int fetch_tags;
28
+ int skip_default_update;
29
30
const char *receivepack;
31
const char *uploadpack;
t/t5505-remote.sh
+2
-2
@@ -97,9 +97,9 @@ test_expect_success 'remove remote' '
97
cat > test/expect << EOF
98
* remote origin
99
URL: $(pwd)/one/.git
100
- Remote branch(es) merged with 'git pull' while on branch master
100
+ Remote branch merged with 'git pull' while on branch master
101
master
102
- New remote branches (next fetch will store in remotes/origin)
102
+ New remote branch (next fetch will store in remotes/origin)
103
master
104
Tracked remote branches
105
side master