diff options
-rw-r--r-- | Documentation/git-remote.txt | 6 | ||||
-rw-r--r-- | builtin-remote.c | 153 | ||||
-rwxr-xr-x | t/t5505-remote.sh | 15 |
3 files changed, 174 insertions, 0 deletions
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index bb99810ec..7b227b307 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git remote' [-v | --verbose] 'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url> +'git remote rename' <old> <new> 'git remote rm' <name> 'git remote show' [-n] <name> 'git remote prune' [-n | --dry-run] <name> @@ -61,6 +62,11 @@ only makes sense in bare repositories. If a remote uses mirror mode, furthermore, `git push` will always behave as if `\--mirror` was passed. +'rename':: + +Rename the remote named <old> to <new>. All remote tracking branches and +configuration settings for the remote are updated. + 'rm':: Remove the remote named <name>. All remote tracking branches and diff --git a/builtin-remote.c b/builtin-remote.c index e396a3ac9..1ca6cdbe2 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -10,6 +10,7 @@ static const char * const builtin_remote_usage[] = { "git remote", "git remote add <name> <url>", + "git remote rename <old> <new>", "git remote rm <name>", "git remote show <name>", "git remote prune <name>", @@ -329,6 +330,156 @@ static int add_branch_for_removal(const char *refname, return 0; } +struct rename_info { + const char *old; + const char *new; + struct string_list *remote_branches; +}; + +static int read_remote_branches(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct rename_info *rename = cb_data; + struct strbuf buf = STRBUF_INIT; + struct string_list_item *item; + int flag; + unsigned char orig_sha1[20]; + const char *symref; + + strbuf_addf(&buf, "refs/remotes/%s", rename->old); + if(!prefixcmp(refname, buf.buf)) { + item = string_list_append(xstrdup(refname), rename->remote_branches); + symref = resolve_ref(refname, orig_sha1, 1, &flag); + if (flag & REF_ISSYMREF) + item->util = xstrdup(symref); + else + item->util = NULL; + } + + return 0; +} + +static int mv(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + struct remote *oldremote, *newremote; + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT; + struct string_list remote_branches = { NULL, 0, 0, 0 }; + struct rename_info rename; + int i; + + if (argc != 3) + usage_with_options(builtin_remote_usage, options); + + rename.old = argv[1]; + rename.new = argv[2]; + rename.remote_branches = &remote_branches; + + oldremote = remote_get(rename.old); + if (!oldremote) + die("No such remote: %s", rename.old); + + newremote = remote_get(rename.new); + if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr)) + die("remote %s already exists.", rename.new); + + strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); + if (!valid_fetch_refspec(buf.buf)) + die("'%s' is not a valid remote name", rename.new); + + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s", rename.old); + strbuf_addf(&buf2, "remote.%s", rename.new); + if (git_config_rename_section(buf.buf, buf2.buf) < 1) + return error("Could not rename config section '%s' to '%s'", + buf.buf, buf2.buf); + + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", rename.new); + if (git_config_set_multivar(buf.buf, NULL, NULL, 1)) + return error("Could not remove config section '%s'", buf.buf); + for (i = 0; i < oldremote->fetch_refspec_nr; i++) { + char *ptr; + + strbuf_reset(&buf2); + strbuf_addstr(&buf2, oldremote->fetch_refspec[i]); + ptr = strstr(buf2.buf, rename.old); + if (ptr) + strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old), + rename.new, strlen(rename.new)); + if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) + return error("Could not append '%s'", buf.buf); + } + + read_branches(); + for (i = 0; i < branch_list.nr; i++) { + struct string_list_item *item = branch_list.items + i; + struct branch_info *info = item->util; + if (info->remote && !strcmp(info->remote, rename.old)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.remote", item->string); + if (git_config_set(buf.buf, rename.new)) { + return error("Could not set '%s'", buf.buf); + } + } + } + + /* + * First remove symrefs, then rename the rest, finally create + * the new symrefs. + */ + for_each_ref(read_remote_branches, &rename); + for (i = 0; i < remote_branches.nr; i++) { + struct string_list_item *item = remote_branches.items + i; + int flag = 0; + unsigned char sha1[20]; + const char *symref; + + symref = resolve_ref(item->string, sha1, 1, &flag); + if (!(flag & REF_ISSYMREF)) + continue; + if (delete_ref(item->string, NULL, REF_NODEREF)) + die("deleting '%s' failed", item->string); + } + for (i = 0; i < remote_branches.nr; i++) { + struct string_list_item *item = remote_branches.items + i; + + if (item->util) + continue; + strbuf_reset(&buf); + strbuf_addstr(&buf, item->string); + strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old), + rename.new, strlen(rename.new)); + strbuf_reset(&buf2); + strbuf_addf(&buf2, "remote: renamed %s to %s", + item->string, buf.buf); + if (rename_ref(item->string, buf.buf, buf2.buf)) + die("renaming '%s' failed", item->string); + } + for (i = 0; i < remote_branches.nr; i++) { + struct string_list_item *item = remote_branches.items + i; + + if (!item->util) + continue; + strbuf_reset(&buf); + strbuf_addstr(&buf, item->string); + strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old), + rename.new, strlen(rename.new)); + strbuf_reset(&buf2); + strbuf_addstr(&buf2, item->util); + strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old), + rename.new, strlen(rename.new)); + strbuf_reset(&buf3); + strbuf_addf(&buf3, "remote: renamed %s to %s", + item->string, buf.buf); + if (create_symref(buf.buf, buf2.buf, buf3.buf)) + die("creating '%s' failed", buf.buf); + } + return 0; +} + static int remove_branches(struct string_list *branches) { int i, result = 0; @@ -695,6 +846,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = show_all(); else if (!strcmp(argv[0], "add")) result = add(argc, argv); + else if (!strcmp(argv[0], "rename")) + result = mv(argc, argv); else if (!strcmp(argv[0], "rm")) result = rm(argc, argv); else if (!strcmp(argv[0], "show")) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index c4380c7e3..0c956bad6 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -328,4 +328,19 @@ test_expect_success 'reject adding remote with an invalid name' ' ' +# The first three test if the tracking branches are properly renamed, +# the last two ones check if the config is updated. + +test_expect_success 'rename a remote' ' + + git clone one four && + (cd four && + git remote rename origin upstream && + rmdir .git/refs/remotes/origin && + test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" && + test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" && + test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" && + test "$(git config branch.master.remote)" = "upstream") + +' test_done |