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
1 change: 1 addition & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,7 @@ fn rewrite_reset_pathspec_separator_args(args: Vec<String>) -> Vec<String> {
if !has_target_before_separator && args.get(separator_index + 1).is_some() {
out.push("HEAD".to_string());
}
out.push("--".to_string());
out.extend(args.iter().skip(separator_index + 1).cloned());
out
}
Expand Down
20 changes: 20 additions & 0 deletions src/command/cloud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2504,6 +2504,22 @@ mod tests {
}
}

async fn enter_isolated_libra_repo() -> (
tempfile::TempDir,
tempfile::TempDir,
ScopedEnvVar,
ScopedEnvVar,
ChangeDirGuard,
) {
let repo = tempdir().unwrap();
let home = tempdir().unwrap();
let home_env = ScopedEnvVar::set("HOME", home.path());
let test_home_env = ScopedEnvVar::set("LIBRA_TEST_HOME", home.path());
setup_with_new_libra_in(repo.path()).await;
let cwd = ChangeDirGuard::new(repo.path());
(repo, home, home_env, test_home_env, cwd)
}

struct ClearedEnvVarGuard {
key: String,
previous: Option<OsString>,
Expand Down Expand Up @@ -2882,7 +2898,9 @@ mod tests {
}

#[tokio::test]
#[serial]
async fn cloud_restore_indexed_objects_downloads_skips_and_verifies_hash() {
let _repo = enter_isolated_libra_repo().await;
let remote = RemoteStorage::new(Arc::new(InMemory::new()));
let local_dir = tempdir().unwrap();
let local = LocalStorage::new(local_dir.path().to_path_buf());
Expand Down Expand Up @@ -2917,7 +2935,9 @@ mod tests {
}

#[tokio::test]
#[serial]
async fn cloud_restore_indexed_objects_reports_hash_mismatch() {
let _repo = enter_isolated_libra_repo().await;
let remote = RemoteStorage::new(Arc::new(InMemory::new()));
let local_dir = tempdir().unwrap();
let local = LocalStorage::new(local_dir.path().to_path_buf());
Expand Down
56 changes: 43 additions & 13 deletions tests/command/file_obliterate_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
//!
//! Layer: L1 (deterministic; tempdir + isolated HOME, no network).

use std::fs;
use std::{
fs,
path::{Path, PathBuf},
time::Duration,
};

use sea_orm::{ConnectOptions, ConnectionTrait, Database, DatabaseConnection, Statement};

use super::{
assert_cli_success, create_committed_repo_via_cli, parse_cli_error_stderr, run_libra_command,
Expand Down Expand Up @@ -36,10 +42,42 @@ fn repo_with_secret() -> (tempfile::TempDir, String) {
(repo, oid)
}

fn loose_path(repo: &std::path::Path, oid: &str) -> std::path::PathBuf {
fn loose_path(repo: &Path, oid: &str) -> PathBuf {
repo.join(".libra/objects").join(&oid[..2]).join(&oid[2..])
}

async fn connect_raw_repo_db(repo: &Path) -> DatabaseConnection {
let db_path = repo.join(".libra").join("libra.db");
let mut opts = ConnectOptions::new(format!("sqlite://{}", db_path.display()));
opts.sqlx_logging(false)
.connect_timeout(Duration::from_secs(5));
Database::connect(opts)
.await
.expect("connect raw repository database")
}

fn mark_obliteration_as_interrupted(repo: &Path) {
let runtime = tokio::runtime::Runtime::new().expect("create runtime for crash-recovery setup");
runtime.block_on(async {
let conn = connect_raw_repo_db(repo).await;
let result = conn
.execute(Statement::from_string(
conn.get_database_backend(),
"UPDATE object_obliteration SET state='obliterating', payload_deleted_at=NULL",
))
.await
.expect("reset obliteration state to 'obliterating'");
assert_eq!(
result.rows_affected(),
1,
"reset exactly one obliteration row to 'obliterating'"
);
conn.close()
.await
.expect("close crash-recovery setup database");
});
}

#[test]
fn obliterate_dry_run_previews_and_deletes_nothing() {
let (repo, oid) = repo_with_secret();
Expand Down Expand Up @@ -148,17 +186,9 @@ fn obliterate_recover_finishes_interrupted() {
"payload restored on disk for the test"
);

// Force the mid-state via sqlite3 — REQUIRED (fail, don't skip, if absent).
let db = p.join(".libra/libra.db");
let status = std::process::Command::new("sqlite3")
.arg(&db)
.arg("UPDATE object_obliteration SET state='obliterating', payload_deleted_at=NULL;")
.status()
.expect("sqlite3 is required for the crash-recovery test");
assert!(
status.success(),
"reset the obliteration state to 'obliterating'"
);
// Force the mid-state in the repository database without relying on host
// sqlite3 binaries, which are not guaranteed on self-hosted runners.
mark_obliteration_as_interrupted(p);

// Recovery must re-delete the payload and finalize.
let recover = run_libra_command(&["file", "obliterate", "--recover"], p);
Expand Down
35 changes: 35 additions & 0 deletions tests/command/reset_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,41 @@ fn reset_double_dash_pathspec_unstages_file_named_like_revision() {
assert_eq!(String::from_utf8_lossy(&status.stdout), " M HEAD\n");
}

#[test]
fn reset_double_dash_preserves_dash_prefixed_pathspecs() {
// Given: tracked files have names that would otherwise parse as flags.
let repo = create_committed_repo_via_cli();
for name in ["-h", "--help"] {
fs::write(repo.path().join(name), format!("{name}\n")).unwrap();
let add = run_libra_command(&["add", "--", name], repo.path());
assert_cli_success(&add, "add dash-prefixed file");
}
let commit = run_libra_command(
&["commit", "-m", "add dash-prefixed files", "--no-verify"],
repo.path(),
);
assert_cli_success(&commit, "commit dash-prefixed files");

for name in ["-h", "--help"] {
fs::write(repo.path().join(name), format!("{name}\nupdated\n")).unwrap();
let add = run_libra_command(&["add", "--", name], repo.path());
assert_cli_success(&add, "stage dash-prefixed file update");

// When: `--` marks the dash-prefixed token as a pathspec.
let output = run_libra_command(&["reset", "--", name], repo.path());

// Then: reset treats the token as a filename instead of a reset/help flag.
assert_cli_success(&output, "reset dash-prefixed pathspec");
let status = run_libra_command(&["status", "--short"], repo.path());
assert_cli_success(&status, "status --short");
let stdout = String::from_utf8_lossy(&status.stdout);
assert!(
stdout.contains(&format!(" M {name}\n")),
"expected {name} to be unstaged, got: {stdout}",
);
}
}

#[test]
fn reset_bare_revision_path_ambiguity_errors_like_git() {
// Given: a token names both a branch and a tracked path.
Expand Down
Loading