Skip to content
Draft
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* open the external editor from the status diff view [[@WaterWhisperer](https://github.com/WaterWhisperer)] ([#2805](https://github.com/gitui-org/gitui/issues/2805))
* automatically convert spaces to dashes when creating or renaming a branch [[@pbouillon]](https//pbouillon.github.io)] ([#2916](https://github.com/gitui-org/gitui/pull/2916))
* support rewording non-HEAD commits when `commit.gpgsign` is enabled (gpg format only) [[@guerinoni](https://github.com/guerinoni)] ([#2959](https://github.com/gitui-org/gitui/pull/2959))
* always preserve topology order before sorting commits by date

### Fixes
* crash when opening submodule ([#2895](https://github.com/gitui-org/gitui/issues/2895))
Expand Down
117 changes: 115 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gitui"
version = "0.28.1"
version = "0.28.1-alpha"
authors = ["extrawurst <mail@rusticorn.com>"]
description = "blazing fast terminal-ui for git"
edition = "2021"
Expand Down Expand Up @@ -41,6 +41,7 @@ easy-cast = "0.5"
filetreelist = { path = "./filetreelist", version = ">=0.6" }
fuzzy-matcher = "0.3"
gh-emoji = { version = "1.0", optional = true }
gleisbau = { version = "0.7.5", default-features = false }
indexmap = "2"
itertools = "0.14"
log = "0.4"
Expand Down
10 changes: 10 additions & 0 deletions asyncgit/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ pub enum GixError {
#[error("gix::status::tree_index::Error error: {0}")]
StatusTreeIndex(#[from] Box<gix::status::tree_index::Error>),

///
#[error("gix::traverse::commit::topo error: {0}")]
Topo(#[from] gix::traverse::commit::topo::Error),

///
#[error("gix::worktree::open_index::Error error: {0}")]
WorktreeOpenIndex(#[from] Box<gix::worktree::open_index::Error>),
Expand Down Expand Up @@ -333,6 +337,12 @@ impl From<gix::status::tree_index::Error> for Error {
}
}

impl From<gix::traverse::commit::topo::Error> for Error {
fn from(error: gix::traverse::commit::topo::Error) -> Self {
Self::Gix(GixError::from(error))
}
}

impl From<gix::worktree::open_index::Error> for GixError {
fn from(error: gix::worktree::open_index::Error) -> Self {
Self::WorktreeOpenIndex(Box::new(error))
Expand Down
109 changes: 108 additions & 1 deletion asyncgit/src/sync/branch/merge_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ mod test {
branch_compare_upstream,
remotes::{fetch, push::push_branch},
tests::{
debug_cmd_print, get_commit_ids, repo_clone,
debug_cmd_print, get_commit_ids, repo_clone, repo_init,
repo_init_bare, write_commit_file, write_commit_file_at,
},
RepoState,
Expand Down Expand Up @@ -278,4 +278,111 @@ mod test {
let commits = get_commit_ids(&clone1, 10);
assert_eq!(commits.len(), 1);
}

/// Verify that the walker preserves topology order. This means that
/// no parent is visited before any of its children.
#[test]
fn test_topology_order() {
/* Test case
a diamon shaped graph, where the common ancestor is younger than
one of its children.

GP--P1--M
\ /
--P2

*/

let (_repo_dir, repo) = repo_init().unwrap();

// 1. Grandparent (GP) - Newest
let gp = write_commit_file_at(
&repo,
"gp.txt",
"gp",
"gp",
Time::new(1000, 0),
);

// 2. Parent 1 (P1) - Older
let p1 = write_commit_file_at(
&repo,
"p1.txt",
"p1",
"p1",
Time::new(500, 0),
);

// 3. Parent 2 (P2) - Older (diverging from GP)
// Reset HEAD to GP so P2 becomes a child of GP
repo.reset(
repo.find_object(gp.into(), None)
.unwrap()
.as_commit()
.unwrap()
.as_object(),
git2::ResetType::Hard,
None,
)
.unwrap();
let p2 = write_commit_file_at(
&repo,
"p2.txt",
"p2",
"p2",
Time::new(400, 0),
);

// 4. Merge commit (M) - The starting point of our walk
// The heap now contains [p1, p2].
// If we pop p1, we add gp. The heap is [gp, p2].
// Because gp(1000) > p2(400), the walker returns gp before p2.
// This is a violation: p2 is a child of gp and must be visited first.
let p1_commit = repo.find_commit(p1.into()).unwrap();
let p2_commit = repo.find_commit(p2.into()).unwrap();
let tree = repo
.find_tree(repo.index().unwrap().write_tree().unwrap())
.unwrap();
let sig = repo.signature().unwrap();
let m = repo
.commit(
Some("HEAD"),
&sig,
&sig,
"Merge p1 into p2",
&tree,
&[&p2_commit, &p1_commit],
)
.unwrap();
let m = CommitId::new(m);

// Expected Topological Order: [M, P1, P2, GP] or [M, P2, P1, GP]
// Actual Defective Order: [M, P1, GP, P2]
// (GP jumps ahead of P2 because 1000 > 400)

let commits = get_commit_ids(&repo, 14);
for (i, id) in commits.iter().enumerate() {
println!("DEBUG: commits[{}] = {:?}", i, id);
// Print the message of the commit to identify it
let repo_path = &repo.path().to_path_buf().into();
let details =
crate::sync::get_commit_details(repo_path, *id)
.unwrap();
println!(
"DEBUG: Message: {:?}",
details.message.map(|m| m.combine())
);
}
println!("DEBUG: Expected M is {:?}", m);
println!("DEBUG: P1 is {:?}", &p1);
println!("DEBUG: P2 is {:?}", &p2);
println!("DEBUG: GP is {:?}", &gp);
assert_eq!(commits[0], m);
assert!(commits.contains(&p1));
assert!(commits.contains(&p2));
assert_eq!(
commits[3], gp,
"Violation: Grandparent must be the last commit"
);
}
}
Loading
Loading