This allows users with different preferences for access methods to the same remote repositories to rewrite each other's URLs by pattern matching across a large set of similiarly set up repositories to each get the desired access. For example, if you don't have a kernel.org account, you might want settings like: [url "git://git.kernel.org/pub/"] aka = master.kernel.org:/pub Then, if you give git a URL like: master.kernel.org:/pub/scm/linux/kernel/git/linville/wireless-2.6.git it will act like you gave it: git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-2.6.git and you can cut-and-paste pull requests in email without fixing them by hand, for example. Signed-off-by: Daniel Barkalow <barkalow@xxxxxxxxxxxx> --- The previous version of [PATCH 1/2] doesn't need any changes. This version has the replacement base in the key, as discussed, and makes the whole thing about urls instead of hosts (so everything is renamed). I also added some tests, which are a little bit lame in the particular context of the test setup. Also, using git-config to set options with complicated keys is a bit awkward. Perhaps it should have support for a command line like: git config --set remote "This is my remote" url http://example.com/repo Documentation/config.txt | 16 ++++++++ Documentation/urls.txt | 23 +++++++++++ remote.c | 97 ++++++++++++++++++++++++++++++++++++++++++++- t/t5516-fetch-push.sh | 31 +++++++++++++++ 4 files changed, 164 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index f2f6a77..44f4c4b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -646,6 +646,11 @@ help.format:: Values 'man', 'info', 'web' and 'html' are supported. 'man' is the default. 'web' and 'html' are the same. +host.<name>.rewritebase:: + Additional base URLs which refer to this host. If a URL + matches this, any access to it will use the URL formed with + the corresponding base URL instead of the given URL. + http.proxy:: Override the HTTP proxy, normally configured using the 'http_proxy' environment variable (see linkgit:curl[1]). This can be overridden @@ -886,6 +891,17 @@ tar.umask:: archiving user's umask will be used instead. See umask(2) and linkgit:git-archive[1]. +url.<base>.aka:: + Any URL that starts with this value will be rewritten to + start, instead, with <base>. In cases where some site serves a + large number of repositories, and serves them with multiple + access methods, and some users need to use different access + methods, this feature allows people to specify any of the + equivalent URLs and have git automatically rewrite the URL to + the best alternative for the particular user, even for a + never-before-seen repository on the site. The effect of + having multiple aka values match is undefined. + user.email:: Your email address to be recorded in any newly created commits. Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 81ac17f..0115af7 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -44,3 +44,26 @@ endif::git-clone[] ifdef::git-clone[] They are equivalent, except the former implies --local option. endif::git-clone[] + + +If there are a large number of similarly-named remote repositories and +you want to use a different format for them (such that the URLs you +use will be rewritten into URLs that work), you can create a +configuration section of the form: + +------------ + [url "<actual url base>"] + aka = <other url base> +------------ + +If you have a section: + +------------ + [host "git://git.host.xz/"] + aka = host.xz:/path/to/ + aka = work: +------------ + +a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be +rewritten in any context that takes a URL to be +"git://git.host.xz/repo.git". diff --git a/remote.c b/remote.c index 457d8a4..f8ee38e 100644 --- a/remote.c +++ b/remote.c @@ -2,6 +2,13 @@ #include "remote.h" #include "refs.h" +struct rewrite { + const char *base; + const char **aka; + int aka_nr; + int aka_alloc; +}; + static struct remote **remotes; static int remotes_alloc; static int remotes_nr; @@ -13,9 +20,33 @@ static int branches_nr; static struct branch *current_branch; static const char *default_remote_name; +static struct rewrite **rewrite; +static int rewrite_alloc; +static int rewrite_nr; + #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; +static const char *alias_url(const char *url) +{ + int i, j; + for (i = 0; i < rewrite_nr; i++) { + if (!rewrite[i]) + continue; + for (j = 0; j < rewrite[i]->aka_nr; j++) { + if (!prefixcmp(url, rewrite[i]->aka[j])) { + char *ret = malloc(strlen(rewrite[i]->base) - + strlen(rewrite[i]->aka[j]) + + strlen(url) + 1); + strcpy(ret, rewrite[i]->base); + strcat(ret, url + strlen(rewrite[i]->aka[j])); + return ret; + } + } + } + return url; +} + static void add_push_refspec(struct remote *remote, const char *ref) { ALLOC_GROW(remote->push_refspec, @@ -38,6 +69,11 @@ static void add_url(struct remote *remote, const char *url) remote->url[remote->url_nr++] = url; } +static void add_url_alias(struct remote *remote, const char *url) +{ + add_url(remote, alias_url(url)); +} + static struct remote *make_remote(const char *name, int len) { struct remote *ret; @@ -95,6 +131,35 @@ static struct branch *make_branch(const char *name, int len) return ret; } +static struct rewrite *make_rewrite(const char *base, int len) +{ + struct rewrite *ret; + int i; + + for (i = 0; i < rewrite_nr; i++) { + if (len ? (!strncmp(base, rewrite[i]->base, len) && + !rewrite[i]->base[len]) : + !strcmp(base, rewrite[i]->base)) + return rewrite[i]; + } + + ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc); + ret = xcalloc(1, sizeof(struct rewrite)); + rewrite[rewrite_nr++] = ret; + if (len) + ret->base = xstrndup(base, len); + else + ret->base = xstrdup(base); + + return ret; +} + +static void add_aka(struct rewrite *rewrite, const char *aka) +{ + ALLOC_GROW(rewrite->aka, rewrite->aka_nr + 1, rewrite->aka_alloc); + rewrite->aka[rewrite->aka_nr++] = aka; +} + static void read_remotes_file(struct remote *remote) { FILE *f = fopen(git_path("remotes/%s", remote->name), "r"); @@ -128,7 +193,7 @@ static void read_remotes_file(struct remote *remote) switch (value_list) { case 0: - add_url(remote, xstrdup(s)); + add_url_alias(remote, xstrdup(s)); break; case 1: add_push_refspec(remote, xstrdup(s)); @@ -180,7 +245,7 @@ static void read_branches_file(struct remote *remote) } else { branch = "refs/heads/master"; } - add_url(remote, p); + add_url_alias(remote, p); add_fetch_refspec(remote, branch); remote->fetch_tags = 1; /* always auto-follow */ } @@ -210,6 +275,19 @@ static int handle_config(const char *key, const char *value) } return 0; } + if (!prefixcmp(key, "url.")) { + struct rewrite *rewrite; + name = key + 5; + subkey = strrchr(name, '.'); + if (!subkey) + return 0; + rewrite = make_rewrite(name, subkey - name); + if (!strcmp(subkey, ".aka")) { + if (!value) + return config_error_nonbool(key); + add_aka(rewrite, xstrdup(value)); + } + } if (prefixcmp(key, "remote.")) return 0; name = key + 7; @@ -261,6 +339,18 @@ static int handle_config(const char *key, const char *value) return 0; } +static void alias_all_urls(void) +{ + int i, j; + for (i = 0; i < remotes_nr; i++) { + if (!remotes[i]) + continue; + for (j = 0; j < remotes[i]->url_nr; j++) { + remotes[i]->url[j] = alias_url(remotes[i]->url[j]); + } + } +} + static void read_config(void) { unsigned char sha1[20]; @@ -277,6 +367,7 @@ static void read_config(void) make_branch(head_ref + strlen("refs/heads/"), 0); } git_config(handle_config); + alias_all_urls(); } struct refspec *parse_ref_spec(int nr_refspec, const char **refspec) @@ -342,7 +433,7 @@ struct remote *remote_get(const char *name) read_branches_file(ret); } if (!ret->url) - add_url(ret, name); + add_url_alias(ret, name); if (!ret->url) return NULL; ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec); diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 9d2dc33..4ffe2a1 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -100,6 +100,23 @@ test_expect_success 'fetch with wildcard' ' ) ' +test_expect_success 'fetch with aka' ' + mk_empty && + ( + TRASH=$(pwd) && + cd testrepo && + git config url./$TRASH/.aka trash/ + git config remote.up.url trash/. && + git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && + git fetch up && + + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push without wildcard' ' mk_empty && @@ -126,6 +143,20 @@ test_expect_success 'push with wildcard' ' ) ' +test_expect_success 'push with aka' ' + mk_empty && + TRASH=$(pwd) && + git config url./$TRASH/.aka trash/ && + git push trash/testrepo refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push with matching heads' ' mk_test heads/master && -- 1.5.4.1.1350.g2b9ee - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html