From bf98421a3363696f3b4c8aedc242bfaadc1ce6f6 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 3 Nov 2008 19:26:18 +0100 Subject: [PATCH 1/4] 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 Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 6 ++ builtin-remote.c | 153 +++++++++++++++++++++++++++++++++++ t/t5505-remote.sh | 15 ++++ 3 files changed, 174 insertions(+) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index bb99810ec7..7b227b307f 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 ] [-m ] [-f] [--mirror] +'git remote rename' 'git remote rm' 'git remote show' [-n] 'git remote prune' [-n | --dry-run] @@ -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 to . All remote tracking branches and +configuration settings for the remote are updated. + 'rm':: Remove the remote named . All remote tracking branches and diff --git a/builtin-remote.c b/builtin-remote.c index e396a3ac90..1ca6cdbe2a 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 ", + "git remote rename ", "git remote rm ", "git remote show ", "git remote prune ", @@ -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 c4380c7e32..0c956bad69 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 From 89cf4c7004ec329c3171448a154d050c8f75874e Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 10 Nov 2008 21:43:00 +0100 Subject: [PATCH 2/4] remote: add a new 'origin' variable to the struct This allows one to track where was the remote's original source, so that it's possible to decide if it makes sense to migrate it to the config format or not. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- remote.c | 3 +++ remote.h | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/remote.c b/remote.c index e530a21e5c..cbb3e484fe 100644 --- a/remote.c +++ b/remote.c @@ -201,6 +201,7 @@ static void read_remotes_file(struct remote *remote) if (!f) return; + remote->origin = REMOTE_REMOTES; while (fgets(buffer, BUF_SIZE, f)) { int value_list; char *s, *p; @@ -261,6 +262,7 @@ static void read_branches_file(struct remote *remote) s++; if (!*s) return; + remote->origin = REMOTE_BRANCHES; p = s + strlen(s); while (isspace(p[-1])) *--p = 0; @@ -350,6 +352,7 @@ static int handle_config(const char *key, const char *value, void *cb) if (!subkey) return error("Config with no key for remote %s", name); remote = make_remote(name, subkey - name); + remote->origin = REMOTE_CONFIG; if (!strcmp(subkey, ".mirror")) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, ".skipdefaultupdate")) diff --git a/remote.h b/remote.h index d2e170ce66..a46a5be131 100644 --- a/remote.h +++ b/remote.h @@ -1,8 +1,15 @@ #ifndef REMOTE_H #define REMOTE_H +enum { + REMOTE_CONFIG, + REMOTE_REMOTES, + REMOTE_BRANCHES +}; + struct remote { const char *name; + int origin; const char **url; int url_nr; From 1dd1239aa33ddd7f159cd183338ef6f71298e29a Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 10 Nov 2008 21:43:01 +0100 Subject: [PATCH 3/4] git-remote rename: migrate from remotes/ and branches/ Remote definition that came from $GIT_DIR/remotes/nick and $GIT_DIR/branches/nick are migrated to [remotes "nick"] section in the configuration file. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-remote.c | 35 +++++++++++++++++++++++++++++++++++ t/t5505-remote.sh | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/builtin-remote.c b/builtin-remote.c index 1ca6cdbe2a..3af18768e5 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -359,6 +359,38 @@ static int read_remote_branches(const char *refname, return 0; } +static int migrate_file(struct remote *remote) +{ + struct strbuf buf = STRBUF_INIT; + int i; + char *path = NULL; + + strbuf_addf(&buf, "remote.%s.url", remote->name); + for (i = 0; i < remote->url_nr; i++) + if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0)) + return error("Could not append '%s' to '%s'", + remote->url[i], buf.buf); + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.push", remote->name); + for (i = 0; i < remote->push_refspec_nr; i++) + if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0)) + return error("Could not append '%s' to '%s'", + remote->push_refspec[i], buf.buf); + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", remote->name); + for (i = 0; i < remote->fetch_refspec_nr; i++) + if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0)) + return error("Could not append '%s' to '%s'", + remote->fetch_refspec[i], buf.buf); + if (remote->origin == REMOTE_REMOTES) + path = git_path("remotes/%s", remote->name); + else if (remote->origin == REMOTE_BRANCHES) + path = git_path("branches/%s", remote->name); + if (path && unlink(path)) + warning("failed to remove '%s'", path); + return 0; +} + static int mv(int argc, const char **argv) { struct option options[] = { @@ -381,6 +413,9 @@ static int mv(int argc, const char **argv) if (!oldremote) die("No such remote: %s", rename.old); + if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) + return migrate_file(oldremote); + newremote = remote_get(rename.new); if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr)) die("remote %s already exists.", rename.new); diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 0c956bad69..1f59960d90 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -343,4 +343,37 @@ test_expect_success 'rename a remote' ' test "$(git config branch.master.remote)" = "upstream") ' + +cat > remotes_origin << EOF +URL: $(pwd)/one +Push: refs/heads/master:refs/heads/upstream +Pull: refs/heads/master:refs/heads/origin +EOF + +test_expect_success 'migrate a remote from named file in $GIT_DIR/remotes' ' + git clone one five && + origin_url=$(pwd)/one && + (cd five && + git remote rm origin && + mkdir -p .git/remotes && + cat ../remotes_origin > .git/remotes/origin && + git remote rename origin origin && + ! test -f .git/remotes/origin && + test "$(git config remote.origin.url)" = "$origin_url" && + test "$(git config remote.origin.push)" = "refs/heads/master:refs/heads/upstream" && + test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin") +' + +test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' + git clone one six && + origin_url=$(pwd)/one && + (cd six && + git remote rm origin && + echo "$origin_url" > .git/branches/origin && + git remote rename origin origin && + ! test -f .git/branches/origin && + test "$(git config remote.origin.url)" = "$origin_url" && + test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin") +' + test_done From 74443f185ed91f3388b0e45838262eb3ec8338aa Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 10 Nov 2008 21:43:03 +0100 Subject: [PATCH 4/4] git-remote: document the migration feature of the rename subcommand Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 7b227b307f..fad983e297 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -66,6 +66,10 @@ was passed. Rename the remote named to . All remote tracking branches and configuration settings for the remote are updated. ++ +In case and are the same, and is a file under +`$GIT_DIR/remotes` or `$GIT_DIR/branches`, the remote is converted to +the configuration file format. 'rm'::