feat: one store directory per checkout
This commit is contained in:
@@ -260,3 +260,25 @@ fn distinct_remotes_do_not_share_one_store_directory() {
|
||||
// the ordinary remote keeps the path it already had
|
||||
assert!(plain.contains("/a/my_api"), "{plain}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_checkouts_of_one_remote_keep_their_own_files() {
|
||||
let case = Case::new("clones");
|
||||
let second = case.second_checkout();
|
||||
case.write(".env", b"FIRST\n");
|
||||
second.write(".env", b"SECOND\n");
|
||||
|
||||
case.ahab(&["link", "add", ".env"]).ok();
|
||||
// no collision, and no --force needed
|
||||
second.ahab(&["link", "add", ".env"]).ok();
|
||||
|
||||
assert_ne!(case.store, second.store);
|
||||
assert_eq!(fs::read(case.path(".env")).unwrap(), b"FIRST\n");
|
||||
assert_eq!(fs::read(second.path(".env")).unwrap(), b"SECOND\n");
|
||||
|
||||
// one restoring leaves the other linked
|
||||
case.ahab(&["link", "restore", "--all"]).ok();
|
||||
assert!(second.is_symlink(".env"));
|
||||
assert_eq!(fs::read(second.path(".env")).unwrap(), b"SECOND\n");
|
||||
second.ahab(&["link", "list"]).ok().says("linked: .env");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#![allow(dead_code)]
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Write as _;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
@@ -15,8 +16,12 @@ use std::{env, fs};
|
||||
// unique per case even with the suite running in parallel
|
||||
static NEXT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub const REMOTE: &str = "https://git.example.org/acme/proj.git";
|
||||
|
||||
pub struct Case {
|
||||
root: PathBuf,
|
||||
// the data home every checkout of a case shares, as one user's would
|
||||
xdg: PathBuf,
|
||||
pub repo: PathBuf,
|
||||
pub store: PathBuf,
|
||||
pub outside: PathBuf,
|
||||
@@ -32,29 +37,41 @@ impl Case {
|
||||
));
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
|
||||
let case = Self {
|
||||
repo: root.join("repo"),
|
||||
store: root
|
||||
.join("xdg/ahab/git.example.org/acme/proj")
|
||||
.to_path_buf(),
|
||||
outside: root.join("outside"),
|
||||
root,
|
||||
};
|
||||
Self::checkout(root.clone(), root.join("xdg"))
|
||||
}
|
||||
|
||||
for dir in [&case.repo, &case.outside] {
|
||||
// a second clone of the same remote, sharing the first one's data home
|
||||
pub fn second_checkout(&self) -> Self {
|
||||
Self::checkout(self.root.join("second"), self.xdg.clone())
|
||||
}
|
||||
|
||||
fn checkout(root: PathBuf, xdg: PathBuf) -> Self {
|
||||
let repo = root.join("repo");
|
||||
let outside = root.join("outside");
|
||||
for dir in [&repo, &outside] {
|
||||
fs::create_dir_all(dir).expect("creating the case directories");
|
||||
}
|
||||
|
||||
// `<dir>-<hash>` of the canonical checkout, as ahab spells it
|
||||
let canonical = fs::canonicalize(&repo).expect("resolving the repository");
|
||||
let store = xdg.join("ahab/git.example.org/acme/proj").join(format!(
|
||||
"repo-{}",
|
||||
fingerprint(canonical.as_os_str().as_bytes())
|
||||
));
|
||||
|
||||
let case = Self {
|
||||
root,
|
||||
xdg,
|
||||
repo,
|
||||
store,
|
||||
outside,
|
||||
};
|
||||
|
||||
case.git(&["init", "-q", "."]);
|
||||
case.git(&["config", "user.email", "test@example.org"]);
|
||||
case.git(&["config", "user.name", "test"]);
|
||||
case.git(&["config", "commit.gpgsign", "false"]);
|
||||
case.git(&[
|
||||
"remote",
|
||||
"add",
|
||||
"origin",
|
||||
"https://git.example.org/acme/proj.git",
|
||||
]);
|
||||
case.git(&["remote", "add", "origin", REMOTE]);
|
||||
case.write("tracked.txt", b"tracked\n");
|
||||
case.git(&["add", "tracked.txt"]);
|
||||
case.git(&["commit", "-qm", "init"]);
|
||||
@@ -84,7 +101,7 @@ impl Case {
|
||||
pub fn ahab<S: AsRef<OsStr>>(&self, args: &[S]) -> Run {
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_ahab"))
|
||||
.current_dir(&self.repo)
|
||||
.env("XDG_DATA_HOME", self.root.join("xdg"))
|
||||
.env("XDG_DATA_HOME", &self.xdg)
|
||||
// the git commands ahab runs must not read the developer's own config
|
||||
.env("GIT_CONFIG_GLOBAL", "/dev/null")
|
||||
.env("GIT_CONFIG_SYSTEM", "/dev/null")
|
||||
@@ -205,3 +222,15 @@ impl Run {
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
// fnv-1a, as the store spells a checkout
|
||||
pub fn fingerprint(bytes: &[u8]) -> String {
|
||||
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
|
||||
for byte in bytes {
|
||||
hash ^= u64::from(*byte);
|
||||
hash = hash.wrapping_mul(0x100_0000_01b3);
|
||||
}
|
||||
|
||||
format!("{:08x}", hash as u32)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user