Skip to content

Commit

Permalink
Merge branch 'master' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
glandium committed Oct 9, 2023
2 parents 30b4c9e + 230cce3 commit ee7b2dd
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 44 deletions.
8 changes: 4 additions & 4 deletions CI/tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ hg.clonebundles-full.git: hg.clonebundles-full.hg hg.git
hg.clonebundles-bz2.git: hg.clonebundles-bz2.hg hg.git
hg.clonebundles-full-bz2.git: hg.clonebundles-full-bz2.hg hg.git
hg.clonebundles.git hg.clonebundles-full.git hg.clonebundles-bz2.git hg.clonebundles-full-bz2.git:
$(HG) -R $< --config serve.other=http --config serve.otherport=88$(NUM) --config web.port=80$(NUM) --config extensions.clonebundles= --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py serve-and-exec -- $(GIT) clone --progress -n hg://localhost:80$(NUM).http/ $@
$(HG) -R $< --config serve.other=http --config serve.otherport=88$(NUM) --config web.port=80$(NUM) --config experimental.httppostargs=true --config extensions.clonebundles= --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py serve-and-exec -- $(GIT) clone --progress -n hg://localhost:80$(NUM).http/ $@
$(call COMPARE_REFS, $(word 2,$^), $@)

hg.pure.git: hg.git
Expand All @@ -179,7 +179,7 @@ hg.http.hg.gitcredentials hg.http.hg.nobundle2.gitcredentials:

hg.http.hg hg.http.hg.nobundle2: %: %.gitcredentials hg.git
$(call HG_INIT, $@)
$(HG) -R $@ --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py --config web.port=80$(NUM) serve-and-exec -- $(GIT) -c credential.helper='store --file=$(CURDIR)/[email protected]' -c cinnabar.data=never -C $(word 2,$^) push hg://localhost:80$(NUM).http/ refs/remotes/origin/*:refs/heads/*
$(HG) -R $@ --config experimental.httppostargs=true --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py --config web.port=80$(NUM) serve-and-exec -- $(GIT) -c credential.helper='store --file=$(CURDIR)/[email protected]' -c cinnabar.data=never -C $(word 2,$^) push hg://localhost:80$(NUM).http/ refs/remotes/origin/*:refs/heads/*

hg.incr.base.git: hg.incr.hg
$(HG) clone -U $< $@.hg
Expand Down Expand Up @@ -213,7 +213,7 @@ hg.cinnabarclone-bundle.git hg.cinnabarclone-bundle-full.git hg.cinnabarclone-gr
hg.cinnabarclone.git hg.cinnabarclone-full.git hg.cinnabarclone-bundle.git hg.cinnabarclone-bundle-full.git hg.cinnabarclone-graft.git hg.cinnabarclone-graft-replace.git: hg.pure.hg
$(HG) clone -U $< $@.hg
echo http://localhost:88$(NUM)/$(word 2,$^) | tee $@.hg/.hg/cinnabar.manifest
$(HG) -R $@.hg --config web.port=80$(NUM) --config serve.other=$(OTHER_SERVER) --config serve.otherport=88$(NUM) --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py --config extensions.cinnabarclone=$(HG_CINNABARCLONE_EXT) serve-and-exec -- $(GIT) clone --progress hg://localhost:80$(NUM).http/ $@
$(HG) -R $@.hg --config web.port=80$(NUM) --config serve.other=$(OTHER_SERVER) --config serve.otherport=88$(NUM) --config experimental.httppostargs=true --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py --config extensions.cinnabarclone=$(HG_CINNABARCLONE_EXT) serve-and-exec -- $(GIT) clone --progress hg://localhost:80$(NUM).http/ $@
$(call COMPARE_REFS, $(or $(word 3,$^),$(word 2,$^)), $@)
$(GIT) -C $@ cinnabar fsck
$(GIT) -C $@ cinnabar fsck --full
Expand All @@ -224,7 +224,7 @@ hg.cinnabarclone-graft-bundle.git: hg.pure.hg
$(GIT) -C $@ cinnabar rollback 0000000000000000000000000000000000000000
$(GIT) -C $@ remote rename origin grafted
(echo http://localhost:88$(NUM)/$(word 2,$^); echo http://localhost:88$(NUM)/$(word 4,$^) graft=$$($(GIT) ls-remote $(CURDIR)/$(word 4,$^) refs/cinnabar/replace/* | awk -F/ '{print $$NF}')) | tee $@.hg/.hg/cinnabar.manifest
$(HG) -R $@.hg --config serve.other=http --config serve.otherport=88$(NUM) --config web.port=80$(NUM) --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py --config extensions.cinnabarclone=$(HG_CINNABARCLONE_EXT) serve-and-exec -- $(GIT) -c cinnabar.graft=true -C $@ fetch --progress hg://localhost:80$(NUM).http/ refs/heads/*:refs/remotes/origin/*
$(HG) -R $@.hg --config serve.other=http --config serve.otherport=88$(NUM) --config web.port=80$(NUM) --config experimental.httppostargs=true --config extensions.x=$(TOPDIR)/CI/hg-serve-exec.py --config extensions.cinnabarclone=$(HG_CINNABARCLONE_EXT) serve-and-exec -- $(GIT) -c cinnabar.graft=true -C $@ fetch --progress hg://localhost:80$(NUM).http/ refs/heads/*:refs/remotes/origin/*
$(call COMPARE_REFS, $(or $(word 3,$^),$(word 2,$^)), $@)
$(GIT) -C $@ cinnabar fsck
$(GIT) -C $@ cinnabar fsck --full
Expand Down
191 changes: 165 additions & 26 deletions src/hg_connect_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::borrow::ToOwned;
use std::convert::AsRef;
use std::ffi::{c_void, CStr, CString, OsStr};
use std::fs::File;
use std::io::{self, copy, stderr, Cursor, Read, Seek, SeekFrom, Write};
Expand Down Expand Up @@ -43,7 +44,7 @@ use crate::libgit::{
credential_fill, curl_errorstr, die, get_active_slot, http_auth, http_follow_config,
run_one_slot, slot_results, ssl_cainfo, HTTP_OK, HTTP_REAUTH,
};
use crate::util::{ImmutBString, OsStrExt, PrefixWriter, ReadExt, SeekExt, SliceExt, ToBoxed};
use crate::util::{ImmutBString, OsStrExt, PrefixWriter, ReadExt, SliceExt, ToBoxed};

mod git_http_state {
use std::ffi::CString;
Expand Down Expand Up @@ -126,14 +127,141 @@ pub struct HgHttpConnection {
* The command results are simply the corresponding HTTP responses.
*/

trait ReadAndSeek: Read + Seek {}
/// A `Read` that knows its exact length and can seek to its beginning.
trait ExactSizeReadRewind: Read {
fn len(&self) -> io::Result<u64>;

impl<T: Read + Seek> ReadAndSeek for T {}
fn rewind(&mut self) -> io::Result<()>;
}

impl ExactSizeReadRewind for File {
fn len(&self) -> io::Result<u64> {
self.metadata().map(|m| m.len())
}

fn rewind(&mut self) -> io::Result<()> {
self.seek(SeekFrom::Start(0)).map(|_| ())
}
}

impl<T: AsRef<[u8]>> ExactSizeReadRewind for Cursor<T> {
fn len(&self) -> io::Result<u64> {
Ok(self.get_ref().as_ref().len().try_into().unwrap())
}

fn rewind(&mut self) -> io::Result<()> {
self.set_position(0);
Ok(())
}
}

enum Body {
None,
Simple(Box<dyn ExactSizeReadRewind + Send>),
Chained(
std::io::Chain<Box<dyn ExactSizeReadRewind + Send>, Box<dyn ExactSizeReadRewind + Send>>,
),
}

impl Body {
fn new() -> Body {
Body::None
}

fn is_some(&self) -> bool {
!matches!(self, Body::None)
}

fn add(&mut self, r: impl ExactSizeReadRewind + Send + 'static) {
let current = mem::replace(self, Body::None);
*self = match current {
Body::None => Body::Simple(Box::new(r)),
Body::Simple(first) => Body::Chained(first.chain(Box::new(r))),
Body::Chained(_) => Body::Chained(
(Box::new(current) as Box<dyn ExactSizeReadRewind + Send>).chain(Box::new(r)),
),
}
}
}

impl Read for Body {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Body::None => Ok(0),
Body::Simple(first) => first.read(buf),
Body::Chained(chained) => chained.read(buf),
}
}
}

impl ExactSizeReadRewind for Body {
fn len(&self) -> io::Result<u64> {
match self {
Body::None => Ok(0),
Body::Simple(first) => first.len(),
Body::Chained(chained) => {
let (first, second) = chained.get_ref();
Ok(first.len()? + second.len()?)
}
}
}

fn rewind(&mut self) -> io::Result<()> {
match self {
Body::None => Ok(()),
Body::Simple(first) => first.rewind(),
Body::Chained(_) => {
let current = mem::replace(self, Body::None);
if let Body::Chained(chained) = current {
let (mut first, mut second) = chained.into_inner();
first.rewind()?;
second.rewind()?;
*self = Body::Chained(first.chain(second));
}
Ok(())
}
}
}
}

#[test]
fn test_exactsize_read_rewind_body() {
let a = "abcd";
let b = "efg";
let c = "hijklm";

let mut body1 = Body::new();
body1.add(Cursor::new(&a[..]));

assert_eq!(body1.len().unwrap(), 4);
assert_eq!(&body1.read_all_to_string().unwrap()[..], "abcd");
body1.rewind().unwrap();
assert_eq!(&body1.read_all_to_string().unwrap()[..], "abcd");

let mut body2 = Body::new();
body2.add(Cursor::new(&a[..]));
body2.add(Cursor::new(&b[..]));

assert_eq!(body2.len().unwrap(), 7);
assert_eq!(&body2.read_all_to_string().unwrap()[..], "abcdefg");
body2.rewind().unwrap();
assert_eq!(&body2.read_all_to_string().unwrap()[..], "abcdefg");

let mut body3 = Body::new();
body3.add(Cursor::new(&a[..]));
body3.add(Cursor::new(&b[..]));
body3.add(Cursor::new(&c[..]));

assert_eq!(body3.len().unwrap(), 13);
assert_eq!(&body3.read_all_to_string().unwrap()[..], "abcdefghijklm");
body3.rewind().unwrap();
assert_eq!(&body3.read_all_to_string().unwrap()[..], "abcdefghijklm");
}

pub struct HttpRequest {
url: Url,
headers: Vec<(String, String)>,
body: Option<Box<dyn ReadAndSeek + Send>>,
body: Body,
follow_redirects: bool,
token: Arc<GitHttpStateToken>,
}
Expand Down Expand Up @@ -171,7 +299,7 @@ impl HttpRequest {
HttpRequest {
url,
headers: Vec::new(),
body: None,
body: Body::new(),
follow_redirects: false,
token: Arc::new(token),
}
Expand All @@ -185,8 +313,8 @@ impl HttpRequest {
self.headers.push((name.to_string(), value.to_string()));
}

fn post_data(&mut self, data: Box<dyn ReadAndSeek + Send>) {
self.body = Some(data);
fn post_data(&mut self, data: impl ExactSizeReadRewind + Send + 'static) {
self.body.add(data);
}

#[allow(clippy::result_large_err)]
Expand Down Expand Up @@ -221,21 +349,21 @@ impl HttpRequest {
http_request_execute as *const c_void,
);
let mut headers = ptr::null_mut();
if let Some(ref mut body) = self.body {
if self.body.is_some() {
curl_easy_setopt(slot.curl, CURLOPT_POST, 1);
curl_easy_setopt(
slot.curl,
CURLOPT_POSTFIELDSIZE_LARGE,
body.stream_len_().unwrap(),
self.body.len().unwrap(),
);
/* Ensure we have no state from a previous attempt that failed because
* of authentication (401). */
body.seek(SeekFrom::Start(0)).unwrap();
curl_easy_setopt(slot.curl, CURLOPT_READDATA, &mut *body);
self.body.rewind().unwrap();
curl_easy_setopt(slot.curl, CURLOPT_READDATA, &mut self.body);
curl_easy_setopt(
slot.curl,
CURLOPT_READFUNCTION,
read_from_read::<&mut (dyn ReadAndSeek + Send)> as *const c_void,
read_from_read::<Body> as *const c_void,
);
curl_easy_setopt(slot.curl, CURLOPT_FOLLOWLOCATION, 0);
headers = curl_slist_append(headers, cstr!("Expect:").as_ptr());
Expand Down Expand Up @@ -474,28 +602,36 @@ impl HgHttpConnection {
.and_then(|s| usize::from_str(s).ok())
.unwrap_or(0);

let httppostargs = self.get_capability(b"httppostargs").is_some();

let mut command_url = self.url.clone();
let mut query_pairs = command_url.query_pairs_mut();
query_pairs.append_pair("cmd", command);
let mut headers = Vec::new();
let mut body = None;

if httpheader > 0 && !args.is_empty() {
if !args.is_empty() && (httppostargs || httpheader > 0) {
let mut encoder = form_urlencoded::Serializer::new(String::new());
for (name, value) in args.iter() {
encoder.append_pair(name, value);
}
let args = encoder.finish();
let mut args = &args[..];
let mut num = 1;
while !args.is_empty() {
let header_name = format!("X-HgArg-{}", num);
num += 1;
let (chunk, remainder) = args.split_at(cmp::min(
args.len(),
httpheader - header_name.len() - ": ".len(),
));
headers.push((header_name, chunk.to_string()));
args = remainder;
if httppostargs {
headers.push(("X-HgArgs-Post".to_string(), args.len().to_string()));
body = Some(args);
} else {
let mut args = &args[..];
let mut num = 1;
while !args.is_empty() {
let header_name = format!("X-HgArg-{}", num);
num += 1;
let (chunk, remainder) = args.split_at(cmp::min(
args.len(),
httpheader - header_name.len() - ": ".len(),
));
headers.push((header_name, chunk.to_string()));
args = remainder;
}
}
} else {
for (name, value) in args.iter() {
Expand All @@ -513,6 +649,9 @@ impl HgHttpConnection {
for (name, value) in headers {
request.header(&name, &value);
}
if let Some(body) = body {
request.post_data(Cursor::new(body));
}
request
}

Expand All @@ -531,7 +670,7 @@ impl HgWireConnection for HgHttpConnection {
let mut http_req = self.start_command_request(command, args);
if command == "pushkey" {
http_req.header("Content-Type", "application/mercurial-0.1");
http_req.post_data(Box::new(Cursor::new(Vec::<u8>::new())));
http_req.post_data(Cursor::new(Vec::<u8>::new()));
}
let mut http_resp = http_req.execute().unwrap();
self.handle_redirect(&http_resp);
Expand Down Expand Up @@ -584,7 +723,7 @@ impl HgWireConnection for HgHttpConnection {

fn push_command(&mut self, input: File, command: &str, args: HgArgs) -> UnbundleResponse {
let mut http_req = self.start_command_request(command, args);
http_req.post_data(Box::new(input));
http_req.post_data(input);
http_req.header("Content-Type", "application/mercurial-0.1");
let mut http_resp = http_req.execute().unwrap();
self.handle_redirect(&http_resp);
Expand Down
4 changes: 2 additions & 2 deletions src/hg_connect_stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::hg_connect::{
use crate::libc::FdFile;
use crate::libcinnabar::{hg_connect_stdio, stdio_finish};
use crate::libgit::child_process;
use crate::util::{ImmutBString, OsStrExt, PrefixWriter, ReadExt, SeekExt};
use crate::util::{ImmutBString, OsStrExt, PrefixWriter, ReadExt};

pub struct HgStdioConnection {
capabilities: HgCapabilities,
Expand Down Expand Up @@ -113,7 +113,7 @@ impl HgWireConnection for HgStdioConnection {
self.proc_in.write_all(&header).unwrap();
drop(header);

let len = input.stream_len_().unwrap();
let len = input.metadata().unwrap().len();
//TODO: chunk in smaller pieces.
writeln!(self.proc_in, "{}", len).unwrap();

Expand Down
13 changes: 1 addition & 12 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::ffi::{CStr, CString, OsStr};
use std::io::{self, LineWriter, Read, Seek, SeekFrom, Write};
use std::io::{self, LineWriter, Read, Write};
#[cfg(unix)]
use std::os::unix::ffi;
#[cfg(windows)]
Expand Down Expand Up @@ -94,17 +94,6 @@ pub trait ReadExt: Read {

impl<T: Read> ReadExt for T {}

pub trait SeekExt: Seek {
fn stream_len_(&mut self) -> io::Result<u64> {
let old_pos = self.stream_position()?;
let len = self.seek(SeekFrom::End(0))?;
self.seek(SeekFrom::Start(old_pos))?;
Ok(len)
}
}

impl<T: Seek> SeekExt for T {}

pub trait SliceExt<C> {
fn splitn_exact<const N: usize>(&self, c: C) -> Option<[&Self; N]>;
fn rsplitn_exact<const N: usize>(&self, c: C) -> Option<[&Self; N]>;
Expand Down

0 comments on commit ee7b2dd

Please sign in to comment.