Implement git remote rename
The new rename subcommand does the followings: 1) Renames the remote.foo configuration section to remote.bar 2) Updates the remote.bar.fetch refspecs 3) Updates the branch.*.remote settings 4) Renames the tracking branches: renames the normal refs and rewrites the symrefs to point to the new refs. Signed-off-by: Miklos Vajna <vmiklos@frugalware.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Miklos Vajna committed
Nov 3, 2008 at 19:26 UTC
bf98421a3363696f3b4c8aedc242bfaadc1ce6f6
3 files changed
+174
Documentation/git-remote.txt
+6
@@ -11,6 +11,7 @@ SYNOPSIS
11
[verse]
12
'git remote' [-v | --verbose]
13
'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
14
+'git remote rename' <old> <new>
15
'git remote rm' <name>
16
'git remote show' [-n] <name>
17
'git remote prune' [-n | --dry-run] <name>
@@ -61,6 +62,11 @@ only makes sense in bare repositories. If a remote uses mirror
62
mode, furthermore, `git push` will always behave as if `\--mirror`
63
was passed.
64
65
+'rename'::
66
+
67
+Rename the remote named <old> to <new>. All remote tracking branches and
68
+configuration settings for the remote are updated.
69
+
70
'rm'::
71
72
Remove the remote named <name>. All remote tracking branches and
builtin-remote.c
+153
@@ -10,6 +10,7 @@
10
static const char * const builtin_remote_usage[] = {
11
"git remote",
12
"git remote add <name> <url>",
13
+ "git remote rename <old> <new>",
14
"git remote rm <name>",
15
"git remote show <name>",
16
"git remote prune <name>",
@@ -329,6 +330,156 @@ static int add_branch_for_removal(const char *refname,
330
return 0;
331
}
332
333
+struct rename_info {
334
+ const char *old;
335
+ const char *new;
336
+ struct string_list *remote_branches;
337
+};
338
+
339
+static int read_remote_branches(const char *refname,
340
+ const unsigned char *sha1, int flags, void *cb_data)
341
+{
342
+ struct rename_info *rename = cb_data;
343
+ struct strbuf buf = STRBUF_INIT;
344
+ struct string_list_item *item;
345
+ int flag;
346
+ unsigned char orig_sha1[20];
347
+ const char *symref;
348
+
349
+ strbuf_addf(&buf, "refs/remotes/%s", rename->old);
350
+ if(!prefixcmp(refname, buf.buf)) {
351
+ item = string_list_append(xstrdup(refname), rename->remote_branches);
352
+ symref = resolve_ref(refname, orig_sha1, 1, &flag);
353
+ if (flag & REF_ISSYMREF)
354
+ item->util = xstrdup(symref);
355
+ else
356
+ item->util = NULL;
357
+ }
358
+
359
+ return 0;
360
+}
361
+
362
+static int mv(int argc, const char **argv)
363
+{
364
+ struct option options[] = {
365
+ OPT_END()
366
+ };
367
+ struct remote *oldremote, *newremote;
368
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
369
+ struct string_list remote_branches = { NULL, 0, 0, 0 };
370
+ struct rename_info rename;
371
+ int i;
372
+
373
+ if (argc != 3)
374
+ usage_with_options(builtin_remote_usage, options);
375
+
376
+ rename.old = argv[1];
377
+ rename.new = argv[2];
378
+ rename.remote_branches = &remote_branches;
379
+
380
+ oldremote = remote_get(rename.old);
381
+ if (!oldremote)
382
+ die("No such remote: %s", rename.old);
383
+
384
+ newremote = remote_get(rename.new);
385
+ if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
386
+ die("remote %s already exists.", rename.new);
387
+
388
+ strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
389
+ if (!valid_fetch_refspec(buf.buf))
390
+ die("'%s' is not a valid remote name", rename.new);
391
+
392
+ strbuf_reset(&buf);
393
+ strbuf_addf(&buf, "remote.%s", rename.old);
394
+ strbuf_addf(&buf2, "remote.%s", rename.new);
395
+ if (git_config_rename_section(buf.buf, buf2.buf) < 1)
396
+ return error("Could not rename config section '%s' to '%s'",
397
+ buf.buf, buf2.buf);
398
+
399
+ strbuf_reset(&buf);
400
+ strbuf_addf(&buf, "remote.%s.fetch", rename.new);
401
+ if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
402
+ return error("Could not remove config section '%s'", buf.buf);
403
+ for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
404
+ char *ptr;
405
+
406
+ strbuf_reset(&buf2);
407
+ strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
408
+ ptr = strstr(buf2.buf, rename.old);
409
+ if (ptr)
410
+ strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
411
+ rename.new, strlen(rename.new));
412
+ if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
413
+ return error("Could not append '%s'", buf.buf);
414
+ }
415
+
416
+ read_branches();
417
+ for (i = 0; i < branch_list.nr; i++) {
418
+ struct string_list_item *item = branch_list.items + i;
419
+ struct branch_info *info = item->util;
420
+ if (info->remote && !strcmp(info->remote, rename.old)) {
421
+ strbuf_reset(&buf);
422
+ strbuf_addf(&buf, "branch.%s.remote", item->string);
423
+ if (git_config_set(buf.buf, rename.new)) {
424
+ return error("Could not set '%s'", buf.buf);
425
+ }
426
+ }
427
+ }
428
+
429
+ /*
430
+ * First remove symrefs, then rename the rest, finally create
431
+ * the new symrefs.
432
+ */
433
+ for_each_ref(read_remote_branches, &rename);
434
+ for (i = 0; i < remote_branches.nr; i++) {
435
+ struct string_list_item *item = remote_branches.items + i;
436
+ int flag = 0;
437
+ unsigned char sha1[20];
438
+ const char *symref;
439
+
440
+ symref = resolve_ref(item->string, sha1, 1, &flag);
441
+ if (!(flag & REF_ISSYMREF))
442
+ continue;
443
+ if (delete_ref(item->string, NULL, REF_NODEREF))
444
+ die("deleting '%s' failed", item->string);
445
+ }
446
+ for (i = 0; i < remote_branches.nr; i++) {
447
+ struct string_list_item *item = remote_branches.items + i;
448
+
449
+ if (item->util)
450
+ continue;
451
+ strbuf_reset(&buf);
452
+ strbuf_addstr(&buf, item->string);
453
+ strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
454
+ rename.new, strlen(rename.new));
455
+ strbuf_reset(&buf2);
456
+ strbuf_addf(&buf2, "remote: renamed %s to %s",
457
+ item->string, buf.buf);
458
+ if (rename_ref(item->string, buf.buf, buf2.buf))
459
+ die("renaming '%s' failed", item->string);
460
+ }
461
+ for (i = 0; i < remote_branches.nr; i++) {
462
+ struct string_list_item *item = remote_branches.items + i;
463
+
464
+ if (!item->util)
465
+ continue;
466
+ strbuf_reset(&buf);
467
+ strbuf_addstr(&buf, item->string);
468
+ strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
469
+ rename.new, strlen(rename.new));
470
+ strbuf_reset(&buf2);
471
+ strbuf_addstr(&buf2, item->util);
472
+ strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old),
473
+ rename.new, strlen(rename.new));
474
+ strbuf_reset(&buf3);
475
+ strbuf_addf(&buf3, "remote: renamed %s to %s",
476
+ item->string, buf.buf);
477
+ if (create_symref(buf.buf, buf2.buf, buf3.buf))
478
+ die("creating '%s' failed", buf.buf);
479
+ }
480
+ return 0;
481
+}
482
+
483
static int remove_branches(struct string_list *branches)
484
{
485
int i, result = 0;
@@ -695,6 +846,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
846
result = show_all();
847
else if (!strcmp(argv[0], "add"))
848
result = add(argc, argv);
849
+ else if (!strcmp(argv[0], "rename"))
850
+ result = mv(argc, argv);
851
else if (!strcmp(argv[0], "rm"))
852
result = rm(argc, argv);
853
else if (!strcmp(argv[0], "show"))
t/t5505-remote.sh
+15
@@ -328,4 +328,19 @@ test_expect_success 'reject adding remote with an invalid name' '
328
329
'
330
331
+# The first three test if the tracking branches are properly renamed,
332
+# the last two ones check if the config is updated.
333
+
334
+test_expect_success 'rename a remote' '
335
+
336
+ git clone one four &&
337
+ (cd four &&
338
+ git remote rename origin upstream &&
339
+ rmdir .git/refs/remotes/origin &&
340
+ test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
341
+ test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
342
+ test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
343
+ test "$(git config branch.master.remote)" = "upstream")
344
+
345
+'
346
test_done