Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ your local remote-tracking refs.
- Recovery discovery has fixed limits of 2,048 carrier commits, 256 distinct
heads, 64 MiB of manifest data, and 1,000,000 estimated cryptographic
operations. It fails closed if any limit is exhausted.
- Shallow and partial clones are not supported. A fresh clone downloads and
verifies the complete history.
- Shallow and partial clones are not supported. Pushing from a shallow
repository is refused before anything is published; run
`git fetch --unshallow` first. A fresh clone downloads and verifies the
complete history.

## Storage contract

Expand Down
11 changes: 11 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ pub fn start_incremental_pack(
new_refs: &BTreeMap<String, String>,
old_refs: &BTreeMap<String, String>,
) -> Result<PackSource> {
let shallow = Command::new("git")
.arg("-C")
.arg(repo)
.args(["rev-parse", "--is-shallow-repository"])
.output()?;
if !shallow.status.success() {
bail!("could not determine whether the source repository is shallow")
}
if String::from_utf8(shallow.stdout)?.trim() == "true" {
bail!("cannot publish from a shallow repository; fetch --unshallow first")
}
let timer = trace::Span::new("git_pack_objects_stream_lifetime");
let stderr = tempfile::tempfile().context("create pack-objects stderr file")?;
let mut child = Command::new("git")
Expand Down
98 changes: 50 additions & 48 deletions tests/end_to_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,56 @@ fn copy_tree(source: &Path, destination: &Path) {
}
}

#[test]
fn shallow_push_is_rejected_before_publication_and_unshallow_recovers() {
let temporary = tempfile::tempdir().unwrap();
let source = temporary.path().join("source");
let shallow = temporary.path().join("shallow");
let destination = temporary.path().join("destination");
let storage = temporary.path().join("encrypted");
initialize_git(&source);
let first = commit(&source, "first\n", "first");
let second = commit(&source, "second\n", "second");
git(
temporary.path(),
&[
"clone",
"--quiet",
"--depth=1",
&format!("file://{}", source.display()),
shallow.to_str().unwrap(),
],
);
assert_eq!(
git(&shallow, &["rev-parse", "--is-shallow-repository"]),
"true"
);
let key = KeyFile::generate();
let encrypted = EncryptedRepository::new(FilesystemStorage::new(&storage), key);
encrypted.initialize().unwrap();
let head_before = fs::read(storage.join("HEAD")).unwrap();
let error = encrypted
.push_ref(&shallow, "refs/heads/main", false)
.unwrap_err();
assert!(error.to_string().contains("shallow"), "{error:#}");
assert_eq!(fs::read(storage.join("HEAD")).unwrap(), head_before);
assert!(encrypted.verify().unwrap().refs.is_empty());
git(&shallow, &["fetch", "--unshallow"]);
encrypted
.push_ref(&shallow, "refs/heads/main", false)
.unwrap();
initialize_git(&destination);
encrypted.fetch_into(&destination, "encrypted").unwrap();
assert_eq!(
git(&destination, &["rev-parse", "refs/remotes/encrypted/main"]),
second
);
assert_eq!(
git(&destination, &["rev-parse", "refs/remotes/encrypted/main^"]),
first
);
}

#[test]
fn pushes_incrementally_and_fetches_into_another_repository() {
let temporary = tempfile::tempdir().unwrap();
Expand Down Expand Up @@ -504,54 +554,6 @@ fn local_publication_advances_verified_frontier_before_observation() {
);
}

#[test]
fn shallow_publication_does_not_advance_verified_frontier() {
let temporary = tempfile::tempdir().unwrap();
let full_source = temporary.path().join("full-source");
let shallow_source = temporary.path().join("shallow-source");
let remote = temporary.path().join("remote");
initialize_git(&full_source);
commit(&full_source, "first\n", "first");
commit(&full_source, "second\n", "second");

let source_url = format!("file://{}", full_source.display());
let clone = Command::new("git")
.args(["clone", "--quiet", "--depth=1", "--no-tags"])
.arg(&source_url)
.arg(&shallow_source)
.output()
.unwrap();
assert!(
clone.status.success(),
"git clone: {}",
String::from_utf8_lossy(&clone.stderr)
);
assert_eq!(
git(&shallow_source, &["rev-parse", "--is-shallow-repository"]),
"true"
);

let key = KeyFile::generate();
let repository = EncryptedRepository::new(FilesystemStorage::new(&remote), key);
repository.initialize().unwrap();
repository
.push_update_for_remote(
&shallow_source,
"origin",
"refs/heads/main",
"refs/heads/main",
false,
)
.unwrap();

assert!(
client_state(&shallow_source, "origin")["verified_refs"]
.as_object()
.unwrap()
.is_empty()
);
}

fn read_stored_object(root: &Path, kind: &str, id: &str) -> Vec<u8> {
fs::read(root.join(kind).join(&id[..2]).join(id)).unwrap()
}
Expand Down
Loading