diff options
-rw-r--r-- | Documentation/config.txt | 11 | ||||
-rw-r--r-- | Documentation/urls.txt | 23 | ||||
-rw-r--r-- | remote.c | 237 | ||||
-rw-r--r-- | remote.h | 4 | ||||
-rwxr-xr-x | t/t5516-fetch-push.sh | 31 |
5 files changed, 231 insertions, 75 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 0c0bd3b0e..8e361a1e7 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -899,6 +899,17 @@ tar.umask:: archiving user's umask will be used instead. See umask(2) and linkgit:git-archive[1]. +url.<base>.insteadOf:: + 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. When more than one + insteadOf strings match a given URL, the longest match is used. + 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 81ac17f32..fa34c6747 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>"] + insteadOf = <other url base> +------------ + +For example, with this: + +------------ + [url "git://git.host.xz/"] + insteadOf = host.xz:/path/to/ + insteadOf = 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". + @@ -2,123 +2,184 @@ #include "remote.h" #include "refs.h" +struct counted_string { + size_t len; + const char *s; +}; +struct rewrite { + const char *base; + size_t baselen; + struct counted_string *instead_of; + int instead_of_nr; + int instead_of_alloc; +}; + static struct remote **remotes; -static int allocated_remotes; +static int remotes_alloc; +static int remotes_nr; static struct branch **branches; -static int allocated_branches; +static int branches_alloc; +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; + char *ret; + struct counted_string *longest; + int longest_i; + + longest = NULL; + longest_i = -1; + for (i = 0; i < rewrite_nr; i++) { + if (!rewrite[i]) + continue; + for (j = 0; j < rewrite[i]->instead_of_nr; j++) { + if (!prefixcmp(url, rewrite[i]->instead_of[j].s) && + (!longest || + longest->len < rewrite[i]->instead_of[j].len)) { + longest = &(rewrite[i]->instead_of[j]); + longest_i = i; + } + } + } + if (!longest) + return url; + + ret = malloc(rewrite[longest_i]->baselen + + (strlen(url) - longest->len) + 1); + strcpy(ret, rewrite[longest_i]->base); + strcpy(ret + rewrite[longest_i]->baselen, url + longest->len); + return ret; +} + static void add_push_refspec(struct remote *remote, const char *ref) { - int nr = remote->push_refspec_nr + 1; - remote->push_refspec = - xrealloc(remote->push_refspec, nr * sizeof(char *)); - remote->push_refspec[nr-1] = ref; - remote->push_refspec_nr = nr; + ALLOC_GROW(remote->push_refspec, + remote->push_refspec_nr + 1, + remote->push_refspec_alloc); + remote->push_refspec[remote->push_refspec_nr++] = ref; } static void add_fetch_refspec(struct remote *remote, const char *ref) { - int nr = remote->fetch_refspec_nr + 1; - remote->fetch_refspec = - xrealloc(remote->fetch_refspec, nr * sizeof(char *)); - remote->fetch_refspec[nr-1] = ref; - remote->fetch_refspec_nr = nr; + ALLOC_GROW(remote->fetch_refspec, + remote->fetch_refspec_nr + 1, + remote->fetch_refspec_alloc); + remote->fetch_refspec[remote->fetch_refspec_nr++] = ref; } static void add_url(struct remote *remote, const char *url) { - int nr = remote->url_nr + 1; - remote->url = - xrealloc(remote->url, nr * sizeof(char *)); - remote->url[nr-1] = url; - remote->url_nr = nr; + ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc); + 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) { - int i, empty = -1; + struct remote *ret; + int i; - for (i = 0; i < allocated_remotes; i++) { - if (!remotes[i]) { - if (empty < 0) - empty = i; - } else { - if (len ? (!strncmp(name, remotes[i]->name, len) && - !remotes[i]->name[len]) : - !strcmp(name, remotes[i]->name)) - return remotes[i]; - } + for (i = 0; i < remotes_nr; i++) { + if (len ? (!strncmp(name, remotes[i]->name, len) && + !remotes[i]->name[len]) : + !strcmp(name, remotes[i]->name)) + return remotes[i]; } - if (empty < 0) { - empty = allocated_remotes; - allocated_remotes += allocated_remotes ? allocated_remotes : 1; - remotes = xrealloc(remotes, - sizeof(*remotes) * allocated_remotes); - memset(remotes + empty, 0, - (allocated_remotes - empty) * sizeof(*remotes)); - } - remotes[empty] = xcalloc(1, sizeof(struct remote)); + ret = xcalloc(1, sizeof(struct remote)); + ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc); + remotes[remotes_nr++] = ret; if (len) - remotes[empty]->name = xstrndup(name, len); + ret->name = xstrndup(name, len); else - remotes[empty]->name = xstrdup(name); - return remotes[empty]; + ret->name = xstrdup(name); + return ret; } static void add_merge(struct branch *branch, const char *name) { - int nr = branch->merge_nr + 1; - branch->merge_name = - xrealloc(branch->merge_name, nr * sizeof(char *)); - branch->merge_name[nr-1] = name; - branch->merge_nr = nr; + ALLOC_GROW(branch->merge_name, branch->merge_nr + 1, + branch->merge_alloc); + branch->merge_name[branch->merge_nr++] = name; } static struct branch *make_branch(const char *name, int len) { - int i, empty = -1; + struct branch *ret; + int i; char *refname; - for (i = 0; i < allocated_branches; i++) { - if (!branches[i]) { - if (empty < 0) - empty = i; - } else { - if (len ? (!strncmp(name, branches[i]->name, len) && - !branches[i]->name[len]) : - !strcmp(name, branches[i]->name)) - return branches[i]; - } + for (i = 0; i < branches_nr; i++) { + if (len ? (!strncmp(name, branches[i]->name, len) && + !branches[i]->name[len]) : + !strcmp(name, branches[i]->name)) + return branches[i]; } - if (empty < 0) { - empty = allocated_branches; - allocated_branches += allocated_branches ? allocated_branches : 1; - branches = xrealloc(branches, - sizeof(*branches) * allocated_branches); - memset(branches + empty, 0, - (allocated_branches - empty) * sizeof(*branches)); - } - branches[empty] = xcalloc(1, sizeof(struct branch)); + ALLOC_GROW(branches, branches_nr + 1, branches_alloc); + ret = xcalloc(1, sizeof(struct branch)); + branches[branches_nr++] = ret; if (len) - branches[empty]->name = xstrndup(name, len); + ret->name = xstrndup(name, len); else - branches[empty]->name = xstrdup(name); + ret->name = xstrdup(name); refname = malloc(strlen(name) + strlen("refs/heads/") + 1); strcpy(refname, "refs/heads/"); - strcpy(refname + strlen("refs/heads/"), - branches[empty]->name); - branches[empty]->refname = refname; + strcpy(refname + strlen("refs/heads/"), ret->name); + ret->refname = refname; - return branches[empty]; + 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 + ? (len == rewrite[i]->baselen && + !strncmp(base, 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); + ret->baselen = len; + } + else { + ret->base = xstrdup(base); + ret->baselen = strlen(base); + } + return ret; +} + +static void add_instead_of(struct rewrite *rewrite, const char *instead_of) +{ + ALLOC_GROW(rewrite->instead_of, rewrite->instead_of_nr + 1, rewrite->instead_of_alloc); + rewrite->instead_of[rewrite->instead_of_nr].s = instead_of; + rewrite->instead_of[rewrite->instead_of_nr].len = strlen(instead_of); + rewrite->instead_of_nr++; } static void read_remotes_file(struct remote *remote) @@ -154,7 +215,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)); @@ -206,7 +267,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 */ } @@ -236,6 +297,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, ".insteadof")) { + if (!value) + return config_error_nonbool(key); + add_instead_of(rewrite, xstrdup(value)); + } + } if (prefixcmp(key, "remote.")) return 0; name = key + 7; @@ -287,6 +361,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]; @@ -303,6 +389,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) @@ -368,7 +455,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); @@ -380,7 +467,7 @@ int for_each_remote(each_remote_fn fn, void *priv) { int i, result = 0; read_config(); - for (i = 0; i < allocated_remotes && !result; i++) { + for (i = 0; i < remotes_nr && !result; i++) { struct remote *r = remotes[i]; if (!r) continue; @@ -6,14 +6,17 @@ struct remote { const char **url; int url_nr; + int url_alloc; const char **push_refspec; struct refspec *push; int push_refspec_nr; + int push_refspec_alloc; const char **fetch_refspec; struct refspec *fetch; int fetch_refspec_nr; + int fetch_refspec_alloc; /* * -1 to never fetch tags @@ -100,6 +103,7 @@ struct branch { const char **merge_name; struct refspec **merge; int merge_nr; + int merge_alloc; }; struct branch *branch_get(const char *name); diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 9d2dc33cb..9023ba05a 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 insteadOf' ' + mk_empty && + ( + TRASH=$(pwd) && + cd testrepo && + git config url./$TRASH/.insteadOf 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 insteadOf' ' + mk_empty && + TRASH=$(pwd) && + git config url./$TRASH/.insteadOf 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 && |