Skip to content

Commit ab7b5a2

Browse files
committed
test: add integration tests for rebase --continue and --abort
Add five integration tests: - rebase_continue_with_remaining_branches: 3-branch chain conflict on branch 2, verify branch 3 gets rebased during --continue - rebase_abort_after_conflict: abort during conflict, verify all branches restored to original OIDs - rebase_continue_no_state: error when no state file exists - rebase_abort_no_state: error when no state file exists - rebase_blocked_when_state_exists: new rebase blocked when prior state file exists, cleaned up with --continue
1 parent 1a446d1 commit ab7b5a2

1 file changed

Lines changed: 384 additions & 0 deletions

File tree

tests/rebase.rs

Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,3 +1693,387 @@ fn rebase_squashed_merge_force_rebase() {
16931693

16941694
teardown_git_repo(repo_name);
16951695
}
1696+
1697+
#[test]
1698+
fn rebase_continue_with_remaining_branches() {
1699+
let repo_name = "rebase_continue_with_remaining_branches";
1700+
let repo = setup_git_repo(repo_name);
1701+
let path_to_repo = generate_path_to_repo(repo_name);
1702+
1703+
{
1704+
create_new_file(&path_to_repo, "hello_world.txt", "Hello, world!");
1705+
first_commit_all(&repo, "first commit");
1706+
};
1707+
1708+
assert_eq!(&get_current_branch_name(&repo), "master");
1709+
1710+
// Create branch_1
1711+
{
1712+
let branch_name = "branch_1";
1713+
create_branch(&repo, branch_name);
1714+
checkout_branch(&repo, branch_name);
1715+
create_new_file(&path_to_repo, "file_1.txt", "contents 1");
1716+
commit_all(&repo, "branch 1 commit");
1717+
};
1718+
1719+
// Create branch_2
1720+
{
1721+
let branch_name = "branch_2";
1722+
create_branch(&repo, branch_name);
1723+
checkout_branch(&repo, branch_name);
1724+
create_new_file(&path_to_repo, "file_2.txt", "contents 2");
1725+
commit_all(&repo, "branch 2 commit");
1726+
};
1727+
1728+
// Create branch_3
1729+
{
1730+
let branch_name = "branch_3";
1731+
create_branch(&repo, branch_name);
1732+
checkout_branch(&repo, branch_name);
1733+
create_new_file(&path_to_repo, "file_3.txt", "contents 3");
1734+
commit_all(&repo, "branch 3 commit");
1735+
};
1736+
1737+
// Set up chain
1738+
let args: Vec<&str> = vec![
1739+
"setup",
1740+
"chain_name",
1741+
"master",
1742+
"branch_1",
1743+
"branch_2",
1744+
"branch_3",
1745+
];
1746+
run_test_bin_expect_ok(&path_to_repo, args);
1747+
1748+
// Create conflict: branch_1 modifies file_2.txt (which branch_2 also has)
1749+
{
1750+
checkout_branch(&repo, "branch_1");
1751+
create_new_file(&path_to_repo, "file_2.txt", "conflict from branch 1");
1752+
commit_all(&repo, "add conflict");
1753+
};
1754+
1755+
// Record original branch_3 position
1756+
let branch_3_oid_before = repo.revparse_single("branch_3").unwrap().id().to_string();
1757+
1758+
// Run git chain rebase — should fail on branch_2
1759+
let args: Vec<&str> = vec!["rebase"];
1760+
let output = run_test_bin_expect_err(&path_to_repo, args);
1761+
1762+
let stderr = console::strip_ansi_codes(&String::from_utf8_lossy(&output.stderr))
1763+
.trim()
1764+
.to_string();
1765+
println!("STDERR: {}", stderr);
1766+
1767+
assert!(
1768+
stderr.contains("Unable to completely rebase branch_2 to branch_1"),
1769+
"stderr should contain rebase failure message, got: {}",
1770+
stderr
1771+
);
1772+
1773+
// Verify state file exists
1774+
let state_file = path_to_repo.join(".git/chain-rebase-state.json");
1775+
assert!(
1776+
state_file.exists(),
1777+
"chain rebase state file should exist after conflict"
1778+
);
1779+
1780+
// Verify branch_3 was NOT rebased yet (still at original position)
1781+
let branch_3_oid_during = repo.revparse_single("branch_3").unwrap().id().to_string();
1782+
assert_eq!(
1783+
branch_3_oid_before, branch_3_oid_during,
1784+
"branch_3 should not have been rebased yet"
1785+
);
1786+
1787+
// Resolve conflict and complete git-level rebase
1788+
commit_all(&repo, "resolve conflict");
1789+
run_git_command(&path_to_repo, vec!["rebase", "--continue"]);
1790+
1791+
assert_eq!(repo.state(), RepositoryState::Clean);
1792+
1793+
// Run git chain rebase --continue — should rebase branch_3
1794+
let args: Vec<&str> = vec!["rebase", "--continue"];
1795+
let output = run_test_bin_for_rebase(&path_to_repo, args);
1796+
1797+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
1798+
println!("CONTINUE STDOUT: {}", stdout);
1799+
1800+
assert!(
1801+
stdout.contains("Continuing chain rebase"),
1802+
"should show continue message, got: {}",
1803+
stdout
1804+
);
1805+
1806+
// Verify branch_3 was rebased (position changed)
1807+
let branch_3_oid_after = repo.revparse_single("branch_3").unwrap().id().to_string();
1808+
assert_ne!(
1809+
branch_3_oid_before, branch_3_oid_after,
1810+
"branch_3 should have been rebased during --continue"
1811+
);
1812+
1813+
// Verify state file was cleaned up
1814+
assert!(
1815+
!state_file.exists(),
1816+
"chain rebase state file should be cleaned up after successful continue"
1817+
);
1818+
1819+
// Verify we're back on the original branch (branch_1)
1820+
assert_eq!(&get_current_branch_name(&repo), "branch_1");
1821+
1822+
teardown_git_repo(repo_name);
1823+
}
1824+
1825+
#[test]
1826+
fn rebase_abort_after_conflict() {
1827+
let repo_name = "rebase_abort_after_conflict";
1828+
let repo = setup_git_repo(repo_name);
1829+
let path_to_repo = generate_path_to_repo(repo_name);
1830+
1831+
{
1832+
create_new_file(&path_to_repo, "hello_world.txt", "Hello, world!");
1833+
first_commit_all(&repo, "first commit");
1834+
};
1835+
1836+
assert_eq!(&get_current_branch_name(&repo), "master");
1837+
1838+
// Create branch_1
1839+
{
1840+
let branch_name = "branch_1";
1841+
create_branch(&repo, branch_name);
1842+
checkout_branch(&repo, branch_name);
1843+
create_new_file(&path_to_repo, "file_1.txt", "contents 1");
1844+
commit_all(&repo, "branch 1 commit");
1845+
};
1846+
1847+
// Create branch_2
1848+
{
1849+
let branch_name = "branch_2";
1850+
create_branch(&repo, branch_name);
1851+
checkout_branch(&repo, branch_name);
1852+
create_new_file(&path_to_repo, "file_2.txt", "contents 2");
1853+
commit_all(&repo, "branch 2 commit");
1854+
};
1855+
1856+
// Set up chain
1857+
let args: Vec<&str> = vec!["setup", "chain_name", "master", "branch_1", "branch_2"];
1858+
run_test_bin_expect_ok(&path_to_repo, args);
1859+
1860+
// Create conflict: branch_1 modifies file_2.txt (which branch_2 also has)
1861+
{
1862+
checkout_branch(&repo, "branch_1");
1863+
create_new_file(&path_to_repo, "file_2.txt", "conflict from branch 1");
1864+
commit_all(&repo, "add conflict");
1865+
};
1866+
1867+
// Record original branch positions
1868+
let branch_2_oid_before = repo.revparse_single("branch_2").unwrap().id().to_string();
1869+
1870+
// Run git chain rebase — should fail on branch_2
1871+
let args: Vec<&str> = vec!["rebase"];
1872+
let output = run_test_bin_expect_err(&path_to_repo, args);
1873+
1874+
let stderr = console::strip_ansi_codes(&String::from_utf8_lossy(&output.stderr))
1875+
.trim()
1876+
.to_string();
1877+
assert!(
1878+
stderr.contains("Unable to completely rebase branch_2 to branch_1"),
1879+
"stderr should contain rebase failure message, got: {}",
1880+
stderr
1881+
);
1882+
1883+
// Verify state file exists
1884+
let state_file = path_to_repo.join(".git/chain-rebase-state.json");
1885+
assert!(
1886+
state_file.exists(),
1887+
"chain rebase state file should exist after conflict"
1888+
);
1889+
1890+
// Run git chain rebase --abort (while git rebase is still in progress)
1891+
let args: Vec<&str> = vec!["rebase", "--abort"];
1892+
let output = run_test_bin_for_rebase(&path_to_repo, args);
1893+
1894+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
1895+
println!("ABORT STDOUT: {}", stdout);
1896+
1897+
assert!(
1898+
stdout.contains("Aborted chain rebase"),
1899+
"should show abort message, got: {}",
1900+
stdout
1901+
);
1902+
assert!(
1903+
stdout.contains("All branches restored"),
1904+
"should show restoration message, got: {}",
1905+
stdout
1906+
);
1907+
1908+
// Verify state file was cleaned up
1909+
assert!(
1910+
!state_file.exists(),
1911+
"chain rebase state file should be cleaned up after abort"
1912+
);
1913+
1914+
// Verify repo is in clean state
1915+
assert_eq!(repo.state(), RepositoryState::Clean);
1916+
1917+
// Verify we're back on the original branch
1918+
assert_eq!(&get_current_branch_name(&repo), "branch_1");
1919+
1920+
// Verify branch_2 is restored to its original position
1921+
let branch_2_oid_after = repo.revparse_single("branch_2").unwrap().id().to_string();
1922+
assert_eq!(
1923+
branch_2_oid_before, branch_2_oid_after,
1924+
"branch_2 should be restored to its original position after abort"
1925+
);
1926+
1927+
teardown_git_repo(repo_name);
1928+
}
1929+
1930+
#[test]
1931+
fn rebase_continue_no_state() {
1932+
let repo_name = "rebase_continue_no_state";
1933+
let repo = setup_git_repo(repo_name);
1934+
let path_to_repo = generate_path_to_repo(repo_name);
1935+
1936+
{
1937+
create_new_file(&path_to_repo, "hello_world.txt", "Hello, world!");
1938+
first_commit_all(&repo, "first commit");
1939+
};
1940+
1941+
// Suppress unused variable warning
1942+
let _ = &repo;
1943+
1944+
// Try --continue with no state file — should fail
1945+
let args: Vec<&str> = vec!["rebase", "--continue"];
1946+
let output = run_test_bin_expect_err(&path_to_repo, args);
1947+
1948+
let stderr = console::strip_ansi_codes(&String::from_utf8_lossy(&output.stderr))
1949+
.trim()
1950+
.to_string();
1951+
println!("STDERR: {}", stderr);
1952+
1953+
assert!(
1954+
stderr.contains("No chain rebase in progress"),
1955+
"stderr should indicate no rebase in progress, got: {}",
1956+
stderr
1957+
);
1958+
1959+
teardown_git_repo(repo_name);
1960+
}
1961+
1962+
#[test]
1963+
fn rebase_abort_no_state() {
1964+
let repo_name = "rebase_abort_no_state";
1965+
let repo = setup_git_repo(repo_name);
1966+
let path_to_repo = generate_path_to_repo(repo_name);
1967+
1968+
{
1969+
create_new_file(&path_to_repo, "hello_world.txt", "Hello, world!");
1970+
first_commit_all(&repo, "first commit");
1971+
};
1972+
1973+
// Suppress unused variable warning
1974+
let _ = &repo;
1975+
1976+
// Try --abort with no state file — should fail
1977+
let args: Vec<&str> = vec!["rebase", "--abort"];
1978+
let output = run_test_bin_expect_err(&path_to_repo, args);
1979+
1980+
let stderr = console::strip_ansi_codes(&String::from_utf8_lossy(&output.stderr))
1981+
.trim()
1982+
.to_string();
1983+
println!("STDERR: {}", stderr);
1984+
1985+
assert!(
1986+
stderr.contains("No chain rebase in progress"),
1987+
"stderr should indicate no rebase in progress, got: {}",
1988+
stderr
1989+
);
1990+
1991+
teardown_git_repo(repo_name);
1992+
}
1993+
1994+
#[test]
1995+
fn rebase_blocked_when_state_exists() {
1996+
let repo_name = "rebase_blocked_when_state_exists";
1997+
let repo = setup_git_repo(repo_name);
1998+
let path_to_repo = generate_path_to_repo(repo_name);
1999+
2000+
{
2001+
create_new_file(&path_to_repo, "hello_world.txt", "Hello, world!");
2002+
first_commit_all(&repo, "first commit");
2003+
};
2004+
2005+
assert_eq!(&get_current_branch_name(&repo), "master");
2006+
2007+
// Create branch_1
2008+
{
2009+
let branch_name = "branch_1";
2010+
create_branch(&repo, branch_name);
2011+
checkout_branch(&repo, branch_name);
2012+
create_new_file(&path_to_repo, "file_1.txt", "contents 1");
2013+
commit_all(&repo, "branch 1 commit");
2014+
};
2015+
2016+
// Create branch_2
2017+
{
2018+
let branch_name = "branch_2";
2019+
create_branch(&repo, branch_name);
2020+
checkout_branch(&repo, branch_name);
2021+
create_new_file(&path_to_repo, "file_2.txt", "contents 2");
2022+
commit_all(&repo, "branch 2 commit");
2023+
};
2024+
2025+
// Set up chain
2026+
let args: Vec<&str> = vec!["setup", "chain_name", "master", "branch_1", "branch_2"];
2027+
run_test_bin_expect_ok(&path_to_repo, args);
2028+
2029+
// Create conflict
2030+
{
2031+
checkout_branch(&repo, "branch_1");
2032+
create_new_file(&path_to_repo, "file_2.txt", "conflict from branch 1");
2033+
commit_all(&repo, "add conflict");
2034+
};
2035+
2036+
// Run git chain rebase — should fail on branch_2
2037+
let args: Vec<&str> = vec!["rebase"];
2038+
run_test_bin_expect_err(&path_to_repo, args);
2039+
2040+
// Resolve git rebase conflict
2041+
commit_all(&repo, "resolve conflict");
2042+
run_git_command(&path_to_repo, vec!["rebase", "--continue"]);
2043+
2044+
// Verify state file still exists
2045+
let state_file = path_to_repo.join(".git/chain-rebase-state.json");
2046+
assert!(state_file.exists(), "chain rebase state file should exist");
2047+
2048+
// Try to run git chain rebase again — should fail because state exists
2049+
let args: Vec<&str> = vec!["rebase"];
2050+
let output = run_test_bin_expect_err(&path_to_repo, args);
2051+
2052+
let stderr = console::strip_ansi_codes(&String::from_utf8_lossy(&output.stderr))
2053+
.trim()
2054+
.to_string();
2055+
println!("STDERR: {}", stderr);
2056+
2057+
assert!(
2058+
stderr.contains("chain rebase is already in progress"),
2059+
"stderr should indicate existing rebase state, got: {}",
2060+
stderr
2061+
);
2062+
assert!(
2063+
stderr.contains("--continue") && stderr.contains("--abort"),
2064+
"stderr should suggest --continue and --abort, got: {}",
2065+
stderr
2066+
);
2067+
2068+
// Clean up by running --continue
2069+
let args: Vec<&str> = vec!["rebase", "--continue"];
2070+
run_test_bin_expect_ok(&path_to_repo, args);
2071+
2072+
// Verify state file is now cleaned up
2073+
assert!(
2074+
!state_file.exists(),
2075+
"chain rebase state file should be cleaned up"
2076+
);
2077+
2078+
teardown_git_repo(repo_name);
2079+
}

0 commit comments

Comments
 (0)