Add url.<base>.pushInsteadOf: URL rewriting for push only
This configuration option allows systematically rewriting fetch-only URLs to push-capable URLs when used with push. For instance: [url "ssh://example.org/"] pushInsteadOf = "git://example.org/" This will allow clones of "git://example.org/path/to/repo" to subsequently push to "ssh://example.org/path/to/repo", without manually configuring pushurl for that remote. Includes documentation for the new option, bash completion updates, and test cases (both that pushInsteadOf applies to push, that it does not apply to fetch, and that it is ignored when pushURL is already defined). Signed-off-by: Josh Triplett <josh@joshtriplett.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Josh Triplett committed
Sep 7, 2009 at 01:56 UTC
1c2eafb89bcaf2ddbf4dfb93df19673c0fadaaeb
5 files changed
+106
-10
Documentation/config.txt
+13
@@ -1500,6 +1500,19 @@ url.<base>.insteadOf::
1500
never-before-seen repository on the site. When more than one
1501
insteadOf strings match a given URL, the longest match is used.
1502
1503
+url.<base>.pushInsteadOf::
1504
+ Any URL that starts with this value will not be pushed to;
1505
+ instead, it will be rewritten to start with <base>, and the
1506
+ resulting URL will be pushed to. In cases where some site serves
1507
+ a large number of repositories, and serves them with multiple
1508
+ access methods, some of which do not allow push, this feature
1509
+ allows people to specify a pull-only URL and have git
1510
+ automatically use an appropriate URL to push, even for a
1511
+ never-before-seen repository on the site. When more than one
1512
+ pushInsteadOf strings match a given URL, the longest match is
1513
+ used. If a remote has an explicit pushurl, git will ignore this
1514
+ setting for that remote.
1515
+
1516
user.email::
1517
Your email address to be recorded in any newly created commits.
1518
Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
Documentation/urls.txt
+18
@@ -67,3 +67,21 @@ For example, with this:
67
a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be
68
rewritten in any context that takes a URL to be "git://git.host.xz/repo.git".
69
70
+If you want to rewrite URLs for push only, you can create a
71
+configuration section of the form:
72
+
73
+------------
74
+ [url "<actual url base>"]
75
+ pushInsteadOf = <other url base>
76
+------------
77
+
78
+For example, with this:
79
+
80
+------------
81
+ [url "ssh://example.org/"]
82
+ pushInsteadOf = git://example.org/
83
+------------
84
+
85
+a URL like "git://example.org/path/to/repo.git" will be rewritten to
86
+"ssh://example.org/path/to/repo.git" for pushes, but pulls will still
87
+use the original URL.
contrib/completion/git-completion.bash
+1
-1
@@ -1532,7 +1532,7 @@ _git_config ()
1532
url.*.*)
1533
local pfx="${cur%.*}."
1534
cur="${cur##*.}"
1535
- __gitcomp "insteadof" "$pfx" "$cur"
1535
+ __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur"
1536
return
1537
;;
1538
esac
remote.c
+27
-9
@@ -47,6 +47,7 @@ static const char *default_remote_name;
47
static int explicit_default_remote_name;
48
49
static struct rewrites rewrites;
50
+static struct rewrites rewrites_push;
51
52
#define BUF_SIZE (2048)
53
static char buffer[BUF_SIZE];
@@ -104,17 +105,25 @@ static void add_url(struct remote *remote, const char *url)
105
remote->url[remote->url_nr++] = url;
106
}
107
107
-static void add_url_alias(struct remote *remote, const char *url)
108
-{
109
- add_url(remote, alias_url(url, &rewrites));
110
-}
111
-
108
static void add_pushurl(struct remote *remote, const char *pushurl)
109
{
110
ALLOC_GROW(remote->pushurl, remote->pushurl_nr + 1, remote->pushurl_alloc);
111
remote->pushurl[remote->pushurl_nr++] = pushurl;
112
}
113
114
+static void add_pushurl_alias(struct remote *remote, const char *url)
115
+{
116
+ const char *pushurl = alias_url(url, &rewrites_push);
117
+ if (pushurl != url)
118
+ add_pushurl(remote, pushurl);
119
+}
120
+
121
+static void add_url_alias(struct remote *remote, const char *url)
122
+{
123
+ add_url(remote, alias_url(url, &rewrites));
124
+ add_pushurl_alias(remote, url);
125
+}
126
+
127
static struct remote *make_remote(const char *name, int len)
128
{
129
struct remote *ret;
@@ -358,8 +367,13 @@ static int handle_config(const char *key, const char *value, void *cb)
367
subkey = strrchr(name, '.');
368
if (!subkey)
369
return 0;
361
- rewrite = make_rewrite(&rewrites, name, subkey - name);
370
if (!strcmp(subkey, ".insteadof")) {
371
+ rewrite = make_rewrite(&rewrites, name, subkey - name);
372
+ if (!value)
373
+ return config_error_nonbool(key);
374
+ add_instead_of(rewrite, xstrdup(value));
375
+ } else if (!strcmp(subkey, ".pushinsteadof")) {
376
+ rewrite = make_rewrite(&rewrites_push, name, subkey - name);
377
if (!value)
378
return config_error_nonbool(key);
379
add_instead_of(rewrite, xstrdup(value));
@@ -433,14 +447,18 @@ static void alias_all_urls(void)
447
{
448
int i, j;
449
for (i = 0; i < remotes_nr; i++) {
450
+ int add_pushurl_aliases;
451
if (!remotes[i])
452
continue;
438
- for (j = 0; j < remotes[i]->url_nr; j++) {
439
- remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites);
440
- }
453
for (j = 0; j < remotes[i]->pushurl_nr; j++) {
454
remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j], &rewrites);
455
}
456
+ add_pushurl_aliases = remotes[i]->pushurl_nr == 0;
457
+ for (j = 0; j < remotes[i]->url_nr; j++) {
458
+ if (add_pushurl_aliases)
459
+ add_pushurl_alias(remotes[i], remotes[i]->url[j]);
460
+ remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites);
461
+ }
462
}
463
}
464
t/t5516-fetch-push.sh
+47
@@ -122,6 +122,23 @@ test_expect_success 'fetch with insteadOf' '
122
)
123
'
124
125
+test_expect_success 'fetch with pushInsteadOf (should not rewrite)' '
126
+ mk_empty &&
127
+ (
128
+ TRASH=$(pwd)/ &&
129
+ cd testrepo &&
130
+ git config "url.trash/.pushInsteadOf" "$TRASH" &&
131
+ git config remote.up.url "$TRASH." &&
132
+ git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
133
+ git fetch up &&
134
+
135
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
136
+ test "z$r" = "z$the_commit" &&
137
+
138
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
139
+ )
140
+'
141
+
142
test_expect_success 'push without wildcard' '
143
mk_empty &&
144
@@ -162,6 +179,36 @@ test_expect_success 'push with insteadOf' '
179
)
180
'
181
182
+test_expect_success 'push with pushInsteadOf' '
183
+ mk_empty &&
184
+ TRASH="$(pwd)/" &&
185
+ git config "url.$TRASH.pushInsteadOf" trash/ &&
186
+ git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
187
+ (
188
+ cd testrepo &&
189
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
190
+ test "z$r" = "z$the_commit" &&
191
+
192
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
193
+ )
194
+'
195
+
196
+test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' '
197
+ mk_empty &&
198
+ TRASH="$(pwd)/" &&
199
+ git config "url.trash2/.pushInsteadOf" trash/ &&
200
+ git config remote.r.url trash/wrong &&
201
+ git config remote.r.pushurl "$TRASH/testrepo" &&
202
+ git push r refs/heads/master:refs/remotes/origin/master &&
203
+ (
204
+ cd testrepo &&
205
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
206
+ test "z$r" = "z$the_commit" &&
207
+
208
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
209
+ )
210
+'
211
+
212
test_expect_success 'push with matching heads' '
213
214
mk_test heads/master &&