use git2::Oid;
use super::*;
fn oid(hex: &str) -> Oid {
let padded = format!("{:0<40}", hex);
Oid::from_str(&padded).unwrap()
}
fn make_commit(hex: &str, message: &str) -> CommitEntry {
CommitEntry {
oid: oid(hex),
short_hash: hex[..7.min(hex.len())].to_string(),
message: message.to_string(),
command: Command::Pick,
update_refs: Vec::new(),
}
}
fn make_commit_with_refs(hex: &str, message: &str, refs: Vec<&str>) -> CommitEntry {
CommitEntry {
oid: oid(hex),
short_hash: hex[..7.min(hex.len())].to_string(),
message: message.to_string(),
command: Command::Pick,
update_refs: refs.into_iter().map(String::from).collect(),
}
}
const BASE: &str = "ba5e000";
const OID_A1: &str = "abc1234";
const OID_A2: &str = "def5678";
const OID_B1: &str = "bbb2222";
const OID_INT: &str = "1111111";
const OID_C1: &str = "ccc3333";
const OID_C2: &str = "ddd4444";
const OID_MERGE1: &str = "9999999";
const OID_MERGE2: &str = "8888888";
const OID_FIX: &str = "eee5555";
#[test]
fn serialize_single_branch_section() {
let graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1"), make_commit(OID_A2, "A2")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_INT, "Int")),
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
],
};
let todo = graph.to_todo();
let lines: Vec<&str> = todo.lines().collect();
assert_eq!(lines[0], "label onto");
assert_eq!(lines[1], "");
assert_eq!(lines[2], "reset onto");
assert_eq!(lines[3], &format!("pick {} # A1", OID_A1));
assert_eq!(lines[4], &format!("pick {} # A2", OID_A2));
assert_eq!(lines[5], "label feature-a");
assert_eq!(lines[6], "update-ref refs/heads/feature-a");
assert_eq!(lines[7], "");
assert_eq!(lines[8], "reset onto");
assert_eq!(lines[9], &format!("pick {} # Int", OID_INT));
assert!(lines[10].starts_with(&format!("merge -C {} feature-a", OID_MERGE1)));
}
#[test]
fn serialize_two_branch_sections() {
let graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
},
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_B1, "B1")],
label: "feature-b".to_string(),
branch_names: vec!["feature-b".to_string()],
},
],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_INT, "Int")),
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE2)),
label: "feature-b".to_string(),
},
],
};
let todo = graph.to_todo();
assert!(todo.contains("label feature-a\n"));
assert!(todo.contains("label feature-b\n"));
assert!(todo.contains(&format!("merge -C {} feature-a", OID_MERGE1)));
assert!(todo.contains(&format!("merge -C {} feature-b", OID_MERGE2)));
}
#[test]
fn serialize_new_merge_without_oid() {
let graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![IntegrationEntry::Merge {
original_oid: None,
label: "feature-a".to_string(),
}],
};
let todo = graph.to_todo();
assert!(todo.contains("merge feature-a # Merge branch 'feature-a'"));
}
#[test]
fn serialize_colocated_branches() {
let graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string(), "feature-b".to_string()],
}],
integration_line: vec![IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
}],
};
let todo = graph.to_todo();
assert!(todo.contains("update-ref refs/heads/feature-a\n"));
assert!(todo.contains("update-ref refs/heads/feature-b\n"));
}
#[test]
fn serialize_update_refs_on_integration_line() {
let graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![IntegrationEntry::Pick(make_commit_with_refs(
OID_C1,
"C1",
vec!["non-woven"],
))],
};
let todo = graph.to_todo();
assert!(todo.contains(&format!("pick {} # C1\n", OID_C1)));
assert!(todo.contains("update-ref refs/heads/non-woven\n"));
}
#[test]
fn serialize_empty_graph() {
let graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![],
};
let todo = graph.to_todo();
assert_eq!(todo, "label onto\n\nreset onto\n");
}
#[test]
fn drop_commit_from_branch_section() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1"), make_commit(OID_A2, "A2")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
}],
};
graph.drop_commit(oid(OID_A1));
assert_eq!(graph.branch_sections.len(), 1);
assert_eq!(graph.branch_sections[0].commits.len(), 1);
assert_eq!(graph.branch_sections[0].commits[0].message, "A2");
}
#[test]
fn drop_last_commit_removes_section_and_merge() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_INT, "Int")),
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
],
};
graph.drop_commit(oid(OID_A1));
assert!(graph.branch_sections.is_empty());
assert_eq!(graph.integration_line.len(), 1); }
#[test]
fn drop_commit_from_integration_line() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Pick(make_commit(OID_C2, "C2")),
],
};
graph.drop_commit(oid(OID_C1));
assert_eq!(graph.integration_line.len(), 1);
}
#[test]
fn drop_branch_removes_section_and_merge() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
},
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_B1, "B1")],
label: "feature-b".to_string(),
branch_names: vec!["feature-b".to_string()],
},
],
integration_line: vec![
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE2)),
label: "feature-b".to_string(),
},
],
};
graph.drop_branch("feature-a");
assert_eq!(graph.branch_sections.len(), 1);
assert_eq!(graph.branch_sections[0].label, "feature-b");
assert_eq!(graph.integration_line.len(), 1);
}
#[test]
fn move_commit_to_branch() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
],
};
graph.move_commit(oid(OID_C1), "feature-a").unwrap();
assert_eq!(graph.branch_sections[0].commits.len(), 2);
assert_eq!(graph.branch_sections[0].commits[1].message, "C1");
assert_eq!(graph.integration_line.len(), 1);
}
#[test]
fn move_commit_to_colocated_branch_splits_section() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string(), "feature-b".to_string()],
},
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_B1, "B1"), make_commit(OID_C1, "C1")],
label: "feature-c".to_string(),
branch_names: vec!["feature-c".to_string()],
},
],
integration_line: vec![
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE2)),
label: "feature-c".to_string(),
},
],
};
graph.move_commit(oid(OID_C1), "feature-b").unwrap();
assert_eq!(graph.branch_sections.len(), 3);
assert_eq!(graph.branch_sections[0].label, "feature-a");
assert_eq!(graph.branch_sections[0].commits.len(), 1);
assert_eq!(graph.branch_sections[0].commits[0].message, "A1");
assert_eq!(
graph.branch_sections[0].branch_names,
vec!["feature-a".to_string()]
);
assert_eq!(graph.branch_sections[1].label, "feature-b");
assert_eq!(graph.branch_sections[1].reset_target, "feature-a");
assert_eq!(graph.branch_sections[1].commits.len(), 1);
assert_eq!(graph.branch_sections[1].commits[0].message, "C1");
assert_eq!(
graph.branch_sections[1].branch_names,
vec!["feature-b".to_string()]
);
assert_eq!(graph.branch_sections[2].label, "feature-c");
assert_eq!(graph.branch_sections[2].commits.len(), 1);
if let IntegrationEntry::Merge { label, .. } = &graph.integration_line[0] {
assert_eq!(label, "feature-b");
} else {
panic!("Expected Merge entry");
}
let todo = graph.to_todo();
assert!(todo.contains("label feature-a\nupdate-ref refs/heads/feature-a\n"));
assert!(todo.contains("reset feature-a\n"));
assert!(todo.contains("label feature-b\nupdate-ref refs/heads/feature-b\n"));
}
#[test]
fn move_commit_to_colocated_branch_when_target_is_label() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string(), "feature-b".to_string()],
}],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
},
],
};
graph.move_commit(oid(OID_C1), "feature-a").unwrap();
assert_eq!(graph.branch_sections.len(), 2);
assert_eq!(graph.branch_sections[0].label, "feature-b");
assert_eq!(
graph.branch_sections[0].branch_names,
vec!["feature-b".to_string()]
);
assert_eq!(graph.branch_sections[1].label, "feature-a");
assert_eq!(graph.branch_sections[1].reset_target, "feature-b");
assert_eq!(graph.branch_sections[1].commits.len(), 1);
assert_eq!(graph.branch_sections[1].commits[0].message, "C1");
assert_eq!(graph.integration_line.len(), 1);
if let IntegrationEntry::Merge { label, .. } = &graph.integration_line[0] {
assert_eq!(label, "feature-a");
} else {
panic!("Expected Merge entry");
}
}
#[test]
fn fixup_commit_moves_and_changes_command() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Pick(make_commit(OID_C2, "C2")),
IntegrationEntry::Pick(make_commit(OID_FIX, "Fixup for C1")),
],
};
graph.fixup_commit(oid(OID_FIX), oid(OID_C1)).unwrap();
assert_eq!(graph.integration_line.len(), 3);
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.message, "C1");
}
if let IntegrationEntry::Pick(c) = &graph.integration_line[1] {
assert_eq!(c.message, "Fixup for C1");
assert_eq!(c.command, Command::Fixup);
}
if let IntegrationEntry::Pick(c) = &graph.integration_line[2] {
assert_eq!(c.message, "C2");
}
}
#[test]
fn move_commit_to_missing_section_errors() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![IntegrationEntry::Pick(make_commit(OID_C1, "C1"))],
};
let result = graph.move_commit(oid(OID_C1), "nonexistent-branch");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("not found"),
"should mention target not found"
);
assert_eq!(graph.integration_line.len(), 1);
}
#[test]
fn fixup_commit_to_missing_target_errors() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Pick(make_commit(OID_FIX, "Fixup")),
],
};
let result = graph.fixup_commit(oid(OID_FIX), oid(OID_C2));
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("not found"),
"should mention target not found"
);
assert_eq!(graph.integration_line.len(), 2);
}
#[test]
fn edit_commit_changes_command() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![IntegrationEntry::Pick(make_commit(OID_C1, "C1"))],
};
graph.edit_commit(oid(OID_C1));
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.command, Command::Edit);
}
}
#[test]
fn add_branch_section_and_merge() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![IntegrationEntry::Pick(make_commit(OID_C1, "C1"))],
};
graph.add_branch_section(
"new-branch".to_string(),
vec!["new-branch".to_string()],
vec![make_commit(OID_B1, "N1")],
"onto".to_string(),
);
graph.add_merge("new-branch".to_string(), None, None);
assert_eq!(graph.branch_sections.len(), 1);
assert_eq!(graph.branch_sections[0].label, "new-branch");
assert_eq!(graph.integration_line.len(), 2);
}
#[test]
fn reassign_branch_renames_section() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string(), "feature-b".to_string()],
}],
integration_line: vec![IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
}],
};
graph.reassign_branch("feature-a", "feature-b");
assert_eq!(graph.branch_sections[0].label, "feature-b");
assert!(
!graph.branch_sections[0]
.branch_names
.contains(&"feature-a".to_string())
);
assert!(
graph.branch_sections[0]
.branch_names
.contains(&"feature-b".to_string())
);
if let IntegrationEntry::Merge { label, .. } = &graph.integration_line[0] {
assert_eq!(label, "feature-b");
}
}
#[test]
fn from_repo_linear_integration() {
use crate::core::test_helpers::TestRepo;
let test_repo = TestRepo::new_with_remote();
test_repo.commit("C1", "c1.txt");
test_repo.commit("C2", "c2.txt");
let graph = Weave::from_repo(&test_repo.repo).unwrap();
assert!(graph.branch_sections.is_empty());
assert_eq!(graph.integration_line.len(), 2);
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.message, "C1");
} else {
panic!("Expected Pick entry");
}
if let IntegrationEntry::Pick(c) = &graph.integration_line[1] {
assert_eq!(c.message, "C2");
} else {
panic!("Expected Pick entry");
}
}
#[test]
fn from_repo_with_woven_branch() {
use crate::core::test_helpers::TestRepo;
let test_repo = TestRepo::new_with_remote();
let base_oid = test_repo.find_remote_branch_target("origin/main");
test_repo.create_branch_at("feature-a", &base_oid.to_string());
test_repo.switch_branch("feature-a");
test_repo.commit("A1", "a1.txt");
test_repo.commit("A2", "a2.txt");
test_repo.switch_branch("integration");
test_repo.commit("Int", "int.txt");
test_repo.merge_no_ff("feature-a");
let graph = Weave::from_repo(&test_repo.repo).unwrap();
assert_eq!(graph.branch_sections.len(), 1);
assert_eq!(graph.branch_sections[0].label, "feature-a");
assert_eq!(graph.branch_sections[0].commits.len(), 2);
assert_eq!(graph.branch_sections[0].commits[0].message, "A1");
assert_eq!(graph.branch_sections[0].commits[1].message, "A2");
assert_eq!(graph.integration_line.len(), 2);
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.message, "Int");
} else {
panic!("Expected Pick entry for Int");
}
if let IntegrationEntry::Merge {
label,
original_oid,
} = &graph.integration_line[1]
{
assert_eq!(label, "feature-a");
assert!(original_oid.is_some());
} else {
panic!("Expected Merge entry for feature-a");
}
}
#[test]
fn from_repo_with_non_woven_branch() {
use crate::core::test_helpers::TestRepo;
let test_repo = TestRepo::new_with_remote();
test_repo.commit("C1", "c1.txt");
let c1_oid = test_repo.head_oid();
test_repo.create_branch_at("feature-a", &c1_oid.to_string());
test_repo.commit("C2", "c2.txt");
let graph = Weave::from_repo(&test_repo.repo).unwrap();
assert!(graph.branch_sections.is_empty());
assert_eq!(graph.integration_line.len(), 2);
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.message, "C1");
assert!(c.update_refs.contains(&"feature-a".to_string()));
} else {
panic!("Expected Pick entry for C1");
}
}
#[test]
fn from_repo_round_trip_preserves_identity() {
use crate::core::test_helpers::TestRepo;
let test_repo = TestRepo::new_with_remote();
let workdir = test_repo.workdir();
let base_oid = test_repo.find_remote_branch_target("origin/main");
test_repo.create_branch_at("feature-a", &base_oid.to_string());
test_repo.switch_branch("feature-a");
test_repo.commit("A1", "a1.txt");
test_repo.switch_branch("integration");
test_repo.commit("Int", "int.txt");
test_repo.merge_no_ff("feature-a");
let messages_before: Vec<String> = {
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
info.commits.iter().map(|c| c.message.clone()).collect()
};
let graph = Weave::from_repo(&test_repo.repo).unwrap();
let todo = graph.to_todo();
run_rebase(workdir.as_path(), Some(&graph.base_oid.to_string()), &todo).unwrap();
let messages_after: Vec<String> = {
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
info.commits.iter().map(|c| c.message.clone()).collect()
};
assert_eq!(messages_before, messages_after);
}
#[test]
fn drop_commit_transfers_update_refs_to_adjacent() {
let mut graph = Weave {
base_oid: oid("aaa"),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
commits: vec![
make_commit("111", "C1"),
make_commit_with_refs("222", "C2", vec!["non-woven-branch"]),
make_commit("333", "C3"),
],
}],
integration_line: vec![IntegrationEntry::Merge {
label: "feature-a".to_string(),
original_oid: None,
}],
};
graph.drop_commit(oid("222"));
assert_eq!(graph.branch_sections[0].commits.len(), 2);
assert!(
graph.branch_sections[0].commits[0]
.update_refs
.contains(&"non-woven-branch".to_string()),
"update_refs should transfer to preceding commit"
);
}
#[test]
fn drop_commit_transfers_update_refs_to_next_when_first() {
let mut graph = Weave {
base_oid: oid("aaa"),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
commits: vec![
make_commit_with_refs("111", "C1", vec!["non-woven-branch"]),
make_commit("222", "C2"),
],
}],
integration_line: vec![IntegrationEntry::Merge {
label: "feature-a".to_string(),
original_oid: None,
}],
};
graph.drop_commit(oid("111"));
assert_eq!(graph.branch_sections[0].commits.len(), 1);
assert!(
graph.branch_sections[0].commits[0]
.update_refs
.contains(&"non-woven-branch".to_string()),
"update_refs should transfer to next commit when first is dropped"
);
}
#[test]
fn drop_commit_on_integration_line_transfers_update_refs() {
let mut graph = Weave {
base_oid: oid("aaa"),
branch_sections: vec![],
integration_line: vec![
IntegrationEntry::Pick(make_commit("111", "C1")),
IntegrationEntry::Pick(make_commit_with_refs("222", "C2", vec!["loose-branch"])),
IntegrationEntry::Pick(make_commit("333", "C3")),
],
};
graph.drop_commit(oid("222"));
let picks: Vec<&CommitEntry> = graph
.integration_line
.iter()
.filter_map(|e| {
if let IntegrationEntry::Pick(c) = e {
Some(c)
} else {
None
}
})
.collect();
assert_eq!(picks.len(), 2);
let has_ref = picks
.iter()
.any(|c| c.update_refs.contains(&"loose-branch".to_string()));
assert!(
has_ref,
"update_refs should transfer to adjacent pick on integration line"
);
}
#[test]
fn drop_branch_preserves_colocated_update_refs_at_boundary() {
let mut graph = Weave {
base_oid: oid("aaa"),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
label: "feat3".to_string(),
branch_names: vec!["feat3".to_string()],
commits: vec![
make_commit_with_refs("111", "C1", vec!["feat1", "feat2"]),
make_commit("222", "C2"),
],
}],
integration_line: vec![IntegrationEntry::Merge {
label: "feat3".to_string(),
original_oid: None,
}],
};
graph.drop_branch("feat3");
assert_eq!(graph.branch_sections[0].label, "feat1");
assert!(
graph.branch_sections[0].commits[0]
.update_refs
.contains(&"feat2".to_string()),
"co-located inner refs must be preserved"
);
}
#[test]
fn weave_branch_moves_picks_into_section() {
let mut graph = Weave {
base_oid: oid("aaa"),
branch_sections: vec![],
integration_line: vec![
IntegrationEntry::Pick(make_commit("111", "C1")),
IntegrationEntry::Pick(make_commit_with_refs("222", "C2", vec!["feature-x"])),
IntegrationEntry::Pick(make_commit("333", "C3")),
],
};
graph.weave_branch("feature-x");
assert_eq!(graph.branch_sections.len(), 1);
assert_eq!(graph.branch_sections[0].label, "feature-x");
assert_eq!(graph.branch_sections[0].commits.len(), 2);
assert_eq!(graph.integration_line.len(), 2);
assert!(
matches!(&graph.integration_line[0], IntegrationEntry::Merge { label, .. } if label == "feature-x"),
"expected Merge(feature-x) at position 0"
);
assert!(
matches!(&graph.integration_line[1], IntegrationEntry::Pick(c) if c.message == "C3"),
"expected Pick(C3) at position 1"
);
}
#[test]
fn swap_commits_on_integration_line() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Pick(make_commit(OID_C2, "C2")),
],
};
graph.swap_commits(oid(OID_C1), oid(OID_C2)).unwrap();
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.message, "C2");
} else {
panic!("Expected Pick");
}
if let IntegrationEntry::Pick(c) = &graph.integration_line[1] {
assert_eq!(c.message, "C1");
} else {
panic!("Expected Pick");
}
}
#[test]
fn swap_commits_in_branch_section() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1"), make_commit(OID_A2, "A2")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![IntegrationEntry::Merge {
original_oid: Some(oid(OID_MERGE1)),
label: "feature-a".to_string(),
}],
};
graph.swap_commits(oid(OID_A1), oid(OID_A2)).unwrap();
assert_eq!(graph.branch_sections[0].commits[0].message, "A2");
assert_eq!(graph.branch_sections[0].commits[1].message, "A1");
}
#[test]
fn swap_commits_across_sections_errors() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
},
BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_B1, "B1")],
label: "feature-b".to_string(),
branch_names: vec!["feature-b".to_string()],
},
],
integration_line: vec![
IntegrationEntry::Merge {
original_oid: None,
label: "feature-a".to_string(),
},
IntegrationEntry::Merge {
original_oid: None,
label: "feature-b".to_string(),
},
],
};
let result = graph.swap_commits(oid(OID_A1), oid(OID_B1));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("different"));
}
#[test]
fn swap_commits_across_section_and_integration_errors() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Merge {
original_oid: None,
label: "feature-a".to_string(),
},
],
};
let result = graph.swap_commits(oid(OID_A1), oid(OID_C1));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("different"));
}
#[test]
fn swap_commits_with_itself_errors() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![IntegrationEntry::Pick(make_commit(OID_C1, "C1"))],
};
let result = graph.swap_commits(oid(OID_C1), oid(OID_C1));
assert!(result.is_err());
}
#[test]
fn swap_commits_not_found_errors() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![],
integration_line: vec![IntegrationEntry::Pick(make_commit(OID_C1, "C1"))],
};
let result = graph.swap_commits(oid(OID_C1), oid(OID_C2));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn swap_commits_on_integration_line_with_interleaved_merge() {
let mut graph = Weave {
base_oid: oid(BASE),
branch_sections: vec![BranchSection {
reset_target: "onto".to_string(),
commits: vec![make_commit(OID_A1, "A1")],
label: "feature-a".to_string(),
branch_names: vec!["feature-a".to_string()],
}],
integration_line: vec![
IntegrationEntry::Pick(make_commit(OID_C1, "C1")),
IntegrationEntry::Merge {
original_oid: None,
label: "feature-a".to_string(),
},
IntegrationEntry::Pick(make_commit(OID_C2, "C2")),
],
};
graph.swap_commits(oid(OID_C1), oid(OID_C2)).unwrap();
if let IntegrationEntry::Pick(c) = &graph.integration_line[0] {
assert_eq!(c.message, "C2");
} else {
panic!("Expected Pick at 0");
}
assert!(matches!(
&graph.integration_line[1],
IntegrationEntry::Merge { .. }
));
if let IntegrationEntry::Pick(c) = &graph.integration_line[2] {
assert_eq!(c.message, "C1");
} else {
panic!("Expected Pick at 2");
}
}