Skip to content

Commit b84cf7b

Browse files
committed
Support sticky redirects in git2-curl.
If you use a gitlab url such as "https://gitlab.com/user/repo", then gitlab will redirect the first GET request to "https://gitlab.com/user/repo.git" (with the `.git` at the end). However, gitlab does not redirect the next POST, causing a 404. This change keeps track of the original redirect so that subsequent actions use the new base url. This roughly mirrors what is done in libgit2's transport. Fixes rust-lang/cargo#6114
1 parent afb653c commit b84cf7b

File tree

1 file changed

+30
-4
lines changed

1 file changed

+30
-4
lines changed

git2-curl/src/lib.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@ use url::Url;
3636

3737
struct CurlTransport {
3838
handle: Arc<Mutex<Easy>>,
39+
/// The URL of the remote server, e.g. "https://github.com/user/repo"
40+
///
41+
/// This is an empty string until the first action is performed.
42+
/// If there is an HTTP redirect, this will be updated with the new URL.
43+
base_url: Arc<Mutex<String>>
3944
}
4045

4146
struct CurlSubtransport {
4247
handle: Arc<Mutex<Easy>>,
4348
service: &'static str,
4449
url_path: &'static str,
45-
base_url: String,
50+
base_url: Arc<Mutex<String>>,
4651
method: &'static str,
4752
reader: Option<Cursor<Vec<u8>>>,
4853
sent_request: bool,
@@ -81,12 +86,19 @@ pub unsafe fn register(handle: Easy) {
8186

8287
fn factory(remote: &git2::Remote, handle: Arc<Mutex<Easy>>)
8388
-> Result<Transport, Error> {
84-
Transport::smart(remote, true, CurlTransport { handle: handle })
89+
Transport::smart(remote, true, CurlTransport {
90+
handle: handle,
91+
base_url: Arc::new(Mutex::new(String::new()))
92+
})
8593
}
8694

8795
impl SmartSubtransport for CurlTransport {
8896
fn action(&self, url: &str, action: Service)
8997
-> Result<Box<SmartSubtransportStream>, Error> {
98+
let mut base_url = self.base_url.lock().unwrap();
99+
if base_url.len() == 0 {
100+
*base_url = url.to_string();
101+
}
90102
let (service, path, method) = match action {
91103
Service::UploadPackLs => {
92104
("upload-pack", "/info/refs?service=git-upload-pack", "GET")
@@ -106,7 +118,7 @@ impl SmartSubtransport for CurlTransport {
106118
handle: self.handle.clone(),
107119
service: service,
108120
url_path: path,
109-
base_url: url.to_string(),
121+
base_url: self.base_url.clone(),
110122
method: method,
111123
reader: None,
112124
sent_request: false,
@@ -130,7 +142,7 @@ impl CurlSubtransport {
130142
let agent = format!("git/1.0 (git2-curl {})", env!("CARGO_PKG_VERSION"));
131143

132144
// Parse our input URL to figure out the host
133-
let url = format!("{}{}", self.base_url, self.url_path);
145+
let url = format!("{}{}", self.base_url.lock().unwrap(), self.url_path);
134146
let parsed = try!(Url::parse(&url).map_err(|_| {
135147
self.err("invalid url, failed to parse")
136148
}));
@@ -230,6 +242,20 @@ impl CurlSubtransport {
230242
// Ok, time to read off some data.
231243
let rdr = Cursor::new(data);
232244
self.reader = Some(rdr);
245+
246+
// If there was a redirect, update the `CurlTransport` with the new base.
247+
if let Ok(Some(effective_url)) = h.effective_url() {
248+
let new_base = if effective_url.ends_with(self.url_path) {
249+
// Strip the action from the end.
250+
&effective_url[..effective_url.len() - self.url_path.len()]
251+
} else {
252+
// I'm not sure if this code path makes sense, but it's what
253+
// libgit does.
254+
effective_url
255+
};
256+
*self.base_url.lock().unwrap() = new_base.to_string();
257+
}
258+
233259
Ok(())
234260
}
235261
}

0 commit comments

Comments
 (0)