Fixes for M-02 to M-14, G-01 to G-14, K-02 to K-04, X-04 to X-14, not yet reviewed

Committed before review so Windows and WSL builds can start.
Each fix was tested and mutation-checked here; the codex review
of each item is still to come, so tasks/BUGS.md stays unticked.

M-02 is mid-review: round 1 findings are fixed (apps/ely-mark/src/tally.rs).
K-01 stays deferred. The ElyMark window test mark-outside.sh failed
once at its crash-recovery step in the last run; not yet looked into.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-10 02:29:19 -04:00
1 parent 6505f35669
commit d59159cea2
75 files changed
+1482 -354

No files matched your search

+18
View File
@@ -428,3 +428,21 @@ fn marker_sizes_git_cannot_read_mean_seven() {
);
}
}
#[test]
fn a_tag_on_a_blob_has_no_date_and_spoils_no_list() {
let dir = tempfile::tempdir().expect("a temp dir");
let root = committed(dir.path());
sh(&root, &["tag", "blobtag", "HEAD:a.txt"]);
let refs = crate::branches::read(&git(), &root).expect("the list reads");
let tag = refs
.iter()
.find(|r| r.name == "blobtag")
.expect("the tag is listed");
assert_eq!(tag.at, None);
assert!(
refs.iter()
.filter(|r| r.name != "blobtag")
.all(|r| r.at.is_some())
);
}
+57
View File
@@ -182,3 +182,60 @@ fn twin_reflog_events_stay_apart() {
.collect();
assert_eq!(found, [2], "it moved one down, alone");
}
#[test]
fn a_shown_file_deleted_since_says_it_changed() {
let dir = tempfile::tempdir().expect("a temp dir");
let root = committed(dir.path());
std::fs::write(root.join("gone.txt"), "x").expect("writes");
let clean = recover::clean_preview(&git(), &root, false).expect("previews");
std::fs::remove_file(root.join("gone.txt")).expect("removes");
let write = Write::Clean {
files: clean.files,
dirs: clean.dirs,
ignored: false,
};
let error = run(&root, write).expect_err("refused");
assert!(
error
.to_string()
.contains("gone.txt changed since the preview"),
"{error}"
);
}
#[test]
fn a_pick_in_one_repository_stays_there() {
let (one, other) = (
tempfile::tempdir().expect("a temp dir"),
tempfile::tempdir().expect("a temp dir"),
);
let (first, second) = (committed(one.path()), committed(other.path()));
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, one.path());
for root in [&first, &second] {
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
}
settle(cx, |app| {
tree.read(app).repos.len() == 2 && branch(tree.read(app)).is_some()
});
let ids = cx.update(|_, app| {
tree.read(app)
.repos
.iter()
.map(|o| o.id)
.collect::<Vec<_>>()
});
cx.update(|_, app| {
tree.update(app, |tree, cx| {
tree.activate(Some(ids[0]), cx);
tree.pick_place(crate::places::Place::Remote("origin".into()), cx);
tree.activate(Some(ids[1]), cx);
})
});
assert!(cx.update(|_, app| {
tree.read(app)
.active()
.is_some_and(|o| o.picked_place.is_none())
}));
}
+64
View File
@@ -256,3 +256,67 @@ fn a_pick_that_conflicts_stops_without_a_dialog() {
});
assert!(cx.update(|_, app| tree.read(app).ask.is_none()));
}
#[test]
fn a_git_am_stopped_on_a_conflict_says_so() {
let dir = tempfile::tempdir().expect("a temp dir");
let (root, _) = conflicted(dir.path());
let patch = sh(&root, &["format-patch", "-1", "side", "--stdout"]);
std::fs::write(dir.path().join("p.patch"), patch).expect("writes");
// `git am` stops on the conflict, as in a terminal.
let stopped = super::git().run(
&root,
&[
"am",
"--3way",
dir.path().join("p.patch").to_str().expect("utf-8"),
],
);
assert!(stopped.is_err(), "the patch conflicts");
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, dir.path());
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
let op = |app: &App| match tree.read(app).active().and_then(|o| o.doing.clone()) {
Some(Ok(Some(doing))) => Some((doing.op, doing.theirs)),
_ => None,
};
settle(cx, |app| op(app).is_some());
assert_eq!(
cx.update(|_, app| op(app)),
Some((crate::ops::Op::Am, "1/1".to_string()))
);
}
#[test]
fn a_settled_conflict_says_so_when_picked_again() {
let dir = tempfile::tempdir().expect("a temp dir");
let (root, _) = conflicted(dir.path());
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, dir.path());
let shell = cx.update(|window, app| {
let shell = ely_ui::tool_root(
ely_platform::AppId::Tree,
tree.clone().into(),
Vec::new(),
window,
app,
);
tree.update(app, |tree, _| tree.root = shell.downgrade());
shell
});
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
settle(cx, |app| branch(tree.read(app)).is_some());
cx.update(|_, app| tree.update(app, |tree, cx| tree.merge("side", cx)));
settle(cx, |app| conflicts(tree.read(app)) == 1);
cx.update(|_, app| {
tree.update(app, |tree, cx| {
tree.show("a.txt", ChangeSection::Conflicted, cx)
})
});
settle(cx, |app| tree.read(app).merging.is_some());
cx.update(|_, app| tree.update(app, |tree, cx| tree.take(0, Take::Theirs, cx)));
cx.update(|_, app| tree.update(app, |tree, cx| tree.take(0, Take::Ours, cx)));
let want = cx.update(|_, app| ely_ui::i18n::text(app, "tree.conflict.taken", &[]));
let toasts = cx.update(|_, app| shell.read(app).toasts(app));
assert!(toasts.iter().any(|(text, _)| *text == want), "{toasts:?}");
}
+38
View File
@@ -433,3 +433,41 @@ fn a_stash_with_an_empty_message_still_lists() {
assert_eq!(shown, ["", "named"]);
assert_eq!(list[0].oid, oid);
}
#[test]
fn a_new_repository_lists_no_moves_and_no_lfs_files() {
let dir = tempfile::tempdir().expect("a temp dir");
let root = dir.path().join("new");
crate::repo::init(&git(), &root).expect("init");
assert_eq!(
crate::recover::reflog(&git(), &root).expect("no reflog error"),
Vec::new()
);
assert_eq!(
crate::structure::lfs(&git(), &root).expect("no LFS error"),
Vec::new()
);
}
#[test]
fn a_worktree_registered_through_a_link_lists_its_real_path() {
let dir = tempfile::tempdir().expect("a temp dir");
let root = committed(dir.path());
let other = dir.path().join("tree");
let path = other.to_str().expect("utf-8");
run(
&root,
Write::Git(words(&["worktree", "add", "-b", "side", "--", path])),
)
.expect("adds");
// Other tools may write the path through a link.
std::os::unix::fs::symlink(dir.path(), dir.path().join("link")).expect("links");
let gitdir = root.join(".git/worktrees/tree/gitdir");
std::fs::write(
&gitdir,
format!("{}\n", dir.path().join("link/tree/.git").display()),
)
.expect("writes");
let list = structure::worktrees(&git(), &root).expect("reads");
assert_eq!(list[1].path, std::fs::canonicalize(&other).expect("real"));
}
+32 -1
View File
@@ -343,7 +343,12 @@ fn a_picked_reflog_event_stays_itself_as_others_arrive() {
panic!("a reflog event");
};
assert!(now.same(was), "the event moved to HEAD@{{2}}");
assert!(cx.update(|_, app| tree.read(app).picked_place.as_ref() == Some(&picked)));
assert!(cx.update(|_, app| {
tree.read(app)
.active()
.and_then(|o| o.picked_place.as_ref())
== Some(&picked)
}));
}
#[test]
@@ -383,3 +388,29 @@ fn a_held_config_lock_waits_for_a_retry_too() {
String::from_utf8_lossy(&read).trim() == "Locked Out"
});
}
#[test]
fn a_read_that_lands_late_leaves_the_users_dialog_open() {
let dir = tempfile::tempdir().expect("a temp dir");
let root = committed(dir.path());
let head = sh(&root, &["rev-parse", "HEAD"]).trim().to_string();
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, dir.path());
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
settle(cx, |app| branch(tree.read(app)).is_some());
cx.update(|_, app| {
tree.update(app, |tree, cx| {
tree.ask_move(&head, cx);
tree.ask = Some(Ask::Failed {
title: "mine".into(),
detail: "".into(),
});
})
});
cx.run_until_parked();
let ask = cx.update(|_, app| match &tree.read(app).ask {
Some(Ask::Failed { title, .. }) => title.to_string(),
_ => "replaced".into(),
});
assert_eq!(ask, "mine");
}
+141
View File
@@ -320,3 +320,144 @@ fn a_commit_from_the_box_shows_in_history_with_its_files() {
};
settle(cx, file);
}
#[test]
fn any_new_pick_leaves_a_file_history() {
use crate::tree::Side;
use ely_gpui_component::git::ChangeSection;
let dir = tempfile::tempdir().expect("a temp dir");
let root = committed(dir.path());
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, dir.path());
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
settle(cx, |app| branch(tree.read(app)).is_some());
let head = sh(&root, &["rev-parse", "HEAD"]).trim().to_string();
let looking = |cx: &mut VisualTestContext| cx.update(|_, app| tree.read(app).look.is_some());
let picks: [(&str, Box<dyn Fn(&mut Tree, &mut gpui::Context<Tree>)>); 3] = [
(
"a tab",
Box::new(|tree, cx| tree.set_side(Side::Branches, cx)),
),
(
"a commit",
Box::new(move |tree, cx| tree.pick_commit(&head, cx)),
),
(
"a file",
Box::new(|tree, cx| tree.show("a.txt", ChangeSection::Unstaged, cx)),
),
];
for (what, pick) in picks {
cx.update(|_, app| tree.update(app, |tree, cx| tree.file_log("a.txt", cx)));
assert!(looking(cx), "the history opens");
cx.update(|_, app| tree.update(app, |tree, cx| pick(tree, cx)));
assert!(!looking(cx), "{what} leaves the history");
}
}
#[test]
fn a_result_that_did_not_save_says_so() {
use crate::work::Write;
let dir = tempfile::tempdir().expect("a temp dir");
let root = committed(dir.path());
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, dir.path());
// The window's toasts live on its root.
let shell = cx.update(|window, app| {
let shell = ely_ui::tool_root(
ely_platform::AppId::Tree,
tree.clone().into(),
Vec::new(),
window,
app,
);
tree.update(app, |tree, _| tree.root = shell.downgrade());
shell
});
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
settle(cx, |app| branch(tree.read(app)).is_some());
let id = cx.update(|_, app| tree.read(app).active().expect("open").id);
let save = Write::Save {
path: "a.txt".into(),
text: "merged".into(),
};
cx.update(|_, app| {
tree.update(app, |tree, cx| {
tree.written(
id,
&save,
Err(std::io::Error::other("disk full")),
false,
cx,
)
})
});
let want =
cx.update(|_, app| ely_ui::i18n::text(app, "tree.error.save", &[("error", "disk full")]));
let toasts = cx.update(|_, app| shell.read(app).toasts(app));
assert!(toasts.iter().any(|(text, _)| *text == want), "{toasts:?}");
}
#[test]
fn unstaging_right_after_a_first_commit_deletes_nothing() {
use crate::work::Write;
let dir = tempfile::tempdir().expect("a temp dir");
let root = dir.path().join("new");
crate::repo::init(&super::git(), &root).expect("init");
std::fs::write(root.join("a.txt"), "one\n").expect("writes");
sh(&root, &["add", "a.txt"]);
let mut cx = TestAppContext::single();
let (tree, cx) = window(&mut cx, dir.path());
cx.update(|_, app| tree.update(app, |tree, cx| tree.open_path(root.clone(), cx)));
settle(cx, |app| {
tree.read(app)
.active()
.is_some_and(|opened| matches!(opened.status, Some(Ok(_))))
});
let id = cx.update(|_, app| tree.read(app).active().expect("open").id);
// Committed outside; the status has not read it yet.
sh(&root, &["commit", "-qm", "first"]);
cx.update(|_, app| tree.update(app, |tree, cx| tree.write(id, Write::UnstageAll, cx)));
settle(cx, |app| {
tree.read(app)
.active()
.is_some_and(|opened| !opened.writing && opened.writes.is_empty())
});
assert_eq!(
sh(&root, &["status", "--porcelain"]),
"",
"nothing staged for deletion"
);
}
#[test]
fn the_repository_menu_offers_only_what_this_platform_builds() {
let dir = tempfile::tempdir().expect("a temp dir");
let mut cx = TestAppContext::single();
let (_tree, cx) = window(&mut cx, dir.path());
let names = cx.update(|_, app| {
crate::menu::menus(app)[0]
.items
.iter()
.filter_map(|item| match item {
gpui::MenuItem::Action { name, .. } => Some(name.to_string()),
_ => None,
})
.collect::<Vec<_>>()
});
let mut said = |key: &str| cx.update(|_, app| ely_ui::i18n::text(app, key, &[]).to_string());
let (terminal, reveal, clone) = (
said("tree.tool.terminal"),
said(crate::tools::REVEAL),
said("tree.clone"),
);
assert_eq!(
names.contains(&terminal),
crate::tools::TERMINAL,
"{names:?}"
);
assert!(
names.contains(&reveal) && names.contains(&clone),
"{names:?}"
);
}