//! Remotes kept on a store, through the command line: `oxy remote add` //! with its store settings, `oxy remote check`, and push, clone and pull //! through a `dir://` store (docs/stores.md). use std::path::Path; use std::process::Command; fn oxy(dir: &Path, home: &Path, args: &[&str]) -> (i32, String, String) { let out = Command::new(env!("CARGO_BIN_EXE_oxy")) .arg("--plain") .args(args) .current_dir(dir) .env("OXY_HOME", home) .env("NO_COLOR", "1") .output() .unwrap(); ( out.status.code().unwrap_or(-1), String::from_utf8_lossy(&out.stdout).to_string(), String::from_utf8_lossy(&out.stderr).to_string(), ) } fn ok(dir: &Path, home: &Path, args: &[&str]) -> String { let (code, out, err) = oxy(dir, home, args); assert_eq!(code, 0, "oxy {args:?}: {out}{err}"); out } #[test] fn store_remotes_through_the_cli() { let tmp = tempfile::tempdir().unwrap(); let home = tmp.path().join("home"); std::fs::create_dir_all(&home).unwrap(); let a = tmp.path().join("a"); ok(tmp.path(), &home, &["init", a.to_str().unwrap()]); std::fs::write(a.join("f.txt"), "hello\n").unwrap(); ok(&a, &home, &["commit", "-m", "first"]); let bucket = tmp.path().join("bucket"); let url = format!("dir://{}", bucket.display()); // Store settings belong to bucket URLs only, and are kept on the URL. let (code, _, err) = oxy( &a, &home, &["remote", "add", "usb", &url, "--profile", "work"], ); assert_ne!(code, 0); assert!(err.contains("--profile") && err.contains("s3://"), "{err}"); ok( &a, &home, &[ "remote", "add", "cloud", "s3://my-bucket/repos/a", "--profile", "work", "--region", "eu-west-2", ], ); let listed = ok(&a, &home, &["remote", "list"]); assert!( listed.contains("s3://my-bucket/repos/a?profile=work®ion=eu-west-2"), "{listed}" ); ok(&a, &home, &["remote", "remove", "cloud"]); // A directory store: check before anything is there, push, check again. ok(&a, &home, &["remote", "add", "usb", &url]); let out = ok(&a, &home, &["remote", "check", "usb"]); assert!( out.contains("Reachable") && out.contains("holds no repository yet") && out.contains("Behaves"), "{out}" ); assert_eq!( std::fs::read_dir(&bucket).unwrap().count(), 0, "the check left nothing behind" ); ok(&a, &home, &["push", "usb"]); let out = ok(&a, &home, &["remote", "check", "usb"]); assert!( out.contains("holds this repository") && out.contains("1 bookmark") && out.contains("1 bundle") && out.contains("Behaves"), "{out}" ); // Clone from the store, push back, pull. let b = tmp.path().join("b"); ok(tmp.path(), &home, &["clone", &url, b.to_str().unwrap()]); assert_eq!(std::fs::read_to_string(b.join("f.txt")).unwrap(), "hello\n"); std::fs::write(b.join("f.txt"), "hello\nagain\n").unwrap(); ok(&b, &home, &["commit", "-m", "second"]); ok(&b, &home, &["push"]); ok(&a, &home, &["pull", "usb"]); assert_eq!( std::fs::read_to_string(a.join("f.txt")).unwrap(), "hello\nagain\n" ); // Two pushes made two bundles; a gc folds them into one, and a clone // after it is whole. let out = ok(&a, &home, &["gc", "--remote", "usb", "--now"]); assert!( out.contains("Folded 2 bundles") && out.contains("removed 2 superseded bundles"), "{out}" ); assert_eq!( std::fs::read_dir(bucket.join("bundles")).unwrap().count(), 1 ); let d = tmp.path().join("d"); ok(tmp.path(), &home, &["clone", &url, d.to_str().unwrap()]); assert_eq!( std::fs::read_to_string(d.join("f.txt")).unwrap(), "hello\nagain\n" ); let (code, _, err) = oxy(&a, &home, &["gc", "--remote", "nope"]); assert_ne!(code, 0); assert!(err.contains("no remote"), "{err}"); // Another repository is told the store is taken. let c = tmp.path().join("c"); ok(tmp.path(), &home, &["init", c.to_str().unwrap()]); std::fs::write(c.join("g.txt"), "other\n").unwrap(); ok(&c, &home, &["commit", "-m", "mine"]); ok(&c, &home, &["remote", "add", "usb", &url]); let out = ok(&c, &home, &["remote", "check", "usb"]); assert!( out.contains("holds another repository") && out.contains("would be refused"), "{out}" ); let (code, _, err) = oxy(&c, &home, &["push", "usb"]); assert_ne!(code, 0); assert!(err.contains("different repository"), "{err}"); } #[test] fn commit_with_paths_says_what_it_left() { let tmp = tempfile::tempdir().unwrap(); let home = tmp.path().join("home"); std::fs::create_dir_all(&home).unwrap(); let a = tmp.path().join("a"); ok(tmp.path(), &home, &["init", a.to_str().unwrap()]); std::fs::write(a.join("f.txt"), "one\n").unwrap(); std::fs::write(a.join("g.txt"), "one\n").unwrap(); ok(&a, &home, &["commit", "-m", "first"]); std::fs::write(a.join("f.txt"), "two\n").unwrap(); std::fs::write(a.join("g.txt"), "two\n").unwrap(); let part = ok(&a, &home, &["commit", "-m", "f only", "f.txt"]); assert!(part.contains("1 path left uncommitted"), "{part}"); let rest = ok(&a, &home, &["commit", "-m", "g", "g.txt"]); assert!(!rest.contains("left uncommitted"), "{rest}"); let status = ok(&a, &home, &["status"]); assert!(status.contains("working copy clean"), "{status}"); }