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:
75 files changed
+1482
-354
No files matched your search
@@ -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())
|
||||
);
|
||||
}
|
||||
@@ -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())
|
||||
}));
|
||||
}
|
||||
@@ -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:?}");
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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:?}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user