Skip to content

Commit 7a8e9a6

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 7a8e9a6

File tree

2 files changed

+28
-6
lines changed

2 files changed

+28
-6
lines changed

git2-curl/src/lib.rs

+27-5
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 `None` until the first action is performed.
42+
/// If there is an HTTP redirect, this will be updated with the new URL.
43+
base_url: Option<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,15 @@ 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 { handle: handle, base_url: None })
8590
}
8691

8792
impl SmartSubtransport for CurlTransport {
88-
fn action(&self, url: &str, action: Service)
93+
fn action(&mut self, url: &str, action: Service)
8994
-> Result<Box<SmartSubtransportStream>, Error> {
95+
if self.base_url.is_none() {
96+
self.base_url = Some(Arc::new(Mutex::new(url.to_string())));
97+
}
9098
let (service, path, method) = match action {
9199
Service::UploadPackLs => {
92100
("upload-pack", "/info/refs?service=git-upload-pack", "GET")
@@ -106,7 +114,7 @@ impl SmartSubtransport for CurlTransport {
106114
handle: self.handle.clone(),
107115
service: service,
108116
url_path: path,
109-
base_url: url.to_string(),
117+
base_url: self.base_url.as_ref().unwrap().clone(),
110118
method: method,
111119
reader: None,
112120
sent_request: false,
@@ -130,7 +138,7 @@ impl CurlSubtransport {
130138
let agent = format!("git/1.0 (git2-curl {})", env!("CARGO_PKG_VERSION"));
131139

132140
// Parse our input URL to figure out the host
133-
let url = format!("{}{}", self.base_url, self.url_path);
141+
let url = format!("{}{}", self.base_url.lock().unwrap(), self.url_path);
134142
let parsed = try!(Url::parse(&url).map_err(|_| {
135143
self.err("invalid url, failed to parse")
136144
}));
@@ -230,6 +238,20 @@ impl CurlSubtransport {
230238
// Ok, time to read off some data.
231239
let rdr = Cursor::new(data);
232240
self.reader = Some(rdr);
241+
242+
// If there was a redirect, update the `CurlTransport` with the new base.
243+
if let Ok(Some(effective_url)) = h.effective_url() {
244+
let new_base = if effective_url.ends_with(self.url_path) {
245+
// Strip the action from the end.
246+
&effective_url[..effective_url.len() - self.url_path.len()]
247+
} else {
248+
// I'm not sure if this code path makes sense, but it's what
249+
// libgit does.
250+
effective_url
251+
};
252+
*self.base_url.lock().unwrap() = new_base.to_string();
253+
}
254+
233255
Ok(())
234256
}
235257
}

src/transport.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub trait SmartSubtransport: Send + 'static {
3939
/// This function is responsible for making any network connections and
4040
/// returns a stream which can be read and written from in order to
4141
/// negotiate the git protocol.
42-
fn action(&self, url: &str, action: Service)
42+
fn action(&mut self, url: &str, action: Service)
4343
-> Result<Box<SmartSubtransportStream>, Error>;
4444

4545
/// Terminates a connection with the remote.

0 commit comments

Comments
 (0)