@@ -1480,3 +1480,229 @@ func TestRebase_ConflictSavesCommitterDateFlag(t *testing.T) {
14801480 assert .True (t , loaded .CommitterDateIsAuthorDate ,
14811481 "saved rebase state should preserve CommitterDateIsAuthorDate flag" )
14821482}
1483+
1484+ // TestRebase_NoTrunk_SkipsTrunkRebase verifies that --no-trunk skips rebasing
1485+ // branch 1 onto trunk but still cascades inter-branch rebases.
1486+ func TestRebase_NoTrunk_SkipsTrunkRebase (t * testing.T ) {
1487+ s := stack.Stack {
1488+ Trunk : stack.BranchRef {Branch : "main" },
1489+ Branches : []stack.BranchRef {
1490+ {Branch : "b1" },
1491+ {Branch : "b2" },
1492+ {Branch : "b3" },
1493+ },
1494+ }
1495+
1496+ tmpDir := t .TempDir ()
1497+ writeStackFile (t , tmpDir , s )
1498+
1499+ var allRebaseCalls []rebaseCall
1500+ var currentCheckedOut string
1501+
1502+ mock := newRebaseMock (tmpDir , "b2" )
1503+ mock .CheckoutBranchFn = func (name string ) error {
1504+ currentCheckedOut = name
1505+ return nil
1506+ }
1507+ mock .RebaseFn = func (base string , opts git.RebaseOpts ) error {
1508+ allRebaseCalls = append (allRebaseCalls , rebaseCall {newBase : base , oldBase : "" , branch : currentCheckedOut })
1509+ return nil
1510+ }
1511+ mock .RebaseOntoFn = func (newBase , oldBase , branch string , opts git.RebaseOpts ) error {
1512+ allRebaseCalls = append (allRebaseCalls , rebaseCall {newBase , oldBase , branch })
1513+ return nil
1514+ }
1515+
1516+ restore := git .SetOps (mock )
1517+ defer restore ()
1518+
1519+ cfg , _ , errR := config .NewTestConfig ()
1520+ cmd := RebaseCmd (cfg )
1521+ cmd .SetArgs ([]string {"--no-trunk" })
1522+ cmd .SetOut (io .Discard )
1523+ cmd .SetErr (io .Discard )
1524+ err := cmd .Execute ()
1525+
1526+ cfg .Err .Close ()
1527+ errOut , _ := io .ReadAll (errR )
1528+ output := string (errOut )
1529+
1530+ assert .NoError (t , err )
1531+
1532+ // Only b2 onto b1 and b3 onto b2 — no rebase onto trunk (main).
1533+ require .Len (t , allRebaseCalls , 2 , "should only rebase b2 and b3 (skip b1 onto trunk)" )
1534+ assert .Equal (t , "b1" , allRebaseCalls [0 ].newBase , "b2 should be rebased onto b1" )
1535+ assert .Equal (t , "b2" , allRebaseCalls [1 ].newBase , "b3 should be rebased onto b2" )
1536+
1537+ assert .Contains (t , output , "without trunk" )
1538+ }
1539+
1540+ // TestRebase_NoTrunk_SkipsFetch verifies that --no-trunk does not call Fetch.
1541+ func TestRebase_NoTrunk_SkipsFetch (t * testing.T ) {
1542+ s := stack.Stack {
1543+ Trunk : stack.BranchRef {Branch : "main" },
1544+ Branches : []stack.BranchRef {
1545+ {Branch : "b1" },
1546+ {Branch : "b2" },
1547+ },
1548+ }
1549+
1550+ tmpDir := t .TempDir ()
1551+ writeStackFile (t , tmpDir , s )
1552+
1553+ fetchCalled := false
1554+
1555+ mock := newRebaseMock (tmpDir , "b1" )
1556+ mock .CheckoutBranchFn = func (name string ) error { return nil }
1557+ mock .RebaseFn = func (base string , opts git.RebaseOpts ) error { return nil }
1558+ mock .RebaseOntoFn = func (newBase , oldBase , branch string , opts git.RebaseOpts ) error { return nil }
1559+ mock .FetchFn = func (remote string ) error {
1560+ fetchCalled = true
1561+ return nil
1562+ }
1563+
1564+ restore := git .SetOps (mock )
1565+ defer restore ()
1566+
1567+ cfg , _ , _ := config .NewTestConfig ()
1568+ cmd := RebaseCmd (cfg )
1569+ cmd .SetArgs ([]string {"--no-trunk" })
1570+ cmd .SetOut (io .Discard )
1571+ cmd .SetErr (io .Discard )
1572+ err := cmd .Execute ()
1573+
1574+ cfg .Out .Close ()
1575+ cfg .Err .Close ()
1576+
1577+ assert .NoError (t , err )
1578+ assert .False (t , fetchCalled , "Fetch should not be called with --no-trunk" )
1579+ }
1580+
1581+ // TestRebase_NoTrunk_SingleBranch verifies that --no-trunk with a single-branch
1582+ // stack has no branches to rebase (since branch 1 onto trunk is skipped).
1583+ func TestRebase_NoTrunk_SingleBranch (t * testing.T ) {
1584+ s := stack.Stack {
1585+ Trunk : stack.BranchRef {Branch : "main" },
1586+ Branches : []stack.BranchRef {
1587+ {Branch : "b1" },
1588+ },
1589+ }
1590+
1591+ tmpDir := t .TempDir ()
1592+ writeStackFile (t , tmpDir , s )
1593+
1594+ mock := newRebaseMock (tmpDir , "b1" )
1595+ mock .CheckoutBranchFn = func (name string ) error { return nil }
1596+
1597+ restore := git .SetOps (mock )
1598+ defer restore ()
1599+
1600+ cfg , _ , errR := config .NewTestConfig ()
1601+ cmd := RebaseCmd (cfg )
1602+ cmd .SetArgs ([]string {"--no-trunk" })
1603+ cmd .SetOut (io .Discard )
1604+ cmd .SetErr (io .Discard )
1605+ err := cmd .Execute ()
1606+
1607+ cfg .Err .Close ()
1608+ errOut , _ := io .ReadAll (errR )
1609+ output := string (errOut )
1610+
1611+ assert .NoError (t , err )
1612+ assert .Contains (t , output , "No branches to rebase" )
1613+ }
1614+
1615+ // TestRebase_NoTrunk_WithUpstack verifies --no-trunk combined with --upstack
1616+ // when the current branch is above index 0. The --no-trunk should not change
1617+ // behavior since --upstack already starts from a non-trunk branch.
1618+ func TestRebase_NoTrunk_WithUpstack (t * testing.T ) {
1619+ s := stack.Stack {
1620+ Trunk : stack.BranchRef {Branch : "main" },
1621+ Branches : []stack.BranchRef {
1622+ {Branch : "b1" },
1623+ {Branch : "b2" },
1624+ {Branch : "b3" },
1625+ },
1626+ }
1627+
1628+ tmpDir := t .TempDir ()
1629+ writeStackFile (t , tmpDir , s )
1630+
1631+ var allRebaseCalls []rebaseCall
1632+ var currentCheckedOut string
1633+
1634+ mock := newRebaseMock (tmpDir , "b2" )
1635+ mock .CheckoutBranchFn = func (name string ) error {
1636+ currentCheckedOut = name
1637+ return nil
1638+ }
1639+ mock .RebaseFn = func (base string , opts git.RebaseOpts ) error {
1640+ allRebaseCalls = append (allRebaseCalls , rebaseCall {newBase : base , oldBase : "" , branch : currentCheckedOut })
1641+ return nil
1642+ }
1643+ mock .RebaseOntoFn = func (newBase , oldBase , branch string , opts git.RebaseOpts ) error {
1644+ allRebaseCalls = append (allRebaseCalls , rebaseCall {newBase , oldBase , branch })
1645+ return nil
1646+ }
1647+
1648+ restore := git .SetOps (mock )
1649+ defer restore ()
1650+
1651+ cfg , _ , _ := config .NewTestConfig ()
1652+ cmd := RebaseCmd (cfg )
1653+ cmd .SetArgs ([]string {"--no-trunk" , "--upstack" })
1654+ cmd .SetOut (io .Discard )
1655+ cmd .SetErr (io .Discard )
1656+ err := cmd .Execute ()
1657+
1658+ cfg .Out .Close ()
1659+ cfg .Err .Close ()
1660+
1661+ assert .NoError (t , err )
1662+ // --upstack from b2 = [b2, b3], --no-trunk doesn't change this since startIdx is already 1
1663+ require .Len (t , allRebaseCalls , 2 , "upstack should rebase b2 and b3" )
1664+ assert .Equal (t , "b1" , allRebaseCalls [0 ].newBase , "b2 should be rebased onto b1" )
1665+ assert .Equal (t , "b2" , allRebaseCalls [1 ].newBase , "b3 should be rebased onto b2" )
1666+ }
1667+
1668+ // TestRebase_NoTrunk_ConflictSavesState verifies that --no-trunk persists the
1669+ // NoTrunk flag in the rebase state when a conflict occurs.
1670+ func TestRebase_NoTrunk_ConflictSavesState (t * testing.T ) {
1671+ s := stack.Stack {
1672+ Trunk : stack.BranchRef {Branch : "main" },
1673+ Branches : []stack.BranchRef {
1674+ {Branch : "b1" },
1675+ {Branch : "b2" },
1676+ {Branch : "b3" },
1677+ },
1678+ }
1679+
1680+ tmpDir := t .TempDir ()
1681+ writeStackFile (t , tmpDir , s )
1682+
1683+ mock := newRebaseMock (tmpDir , "b2" )
1684+ mock .CheckoutBranchFn = func (name string ) error { return nil }
1685+ mock .RebaseOntoFn = func (newBase , oldBase , branch string , opts git.RebaseOpts ) error {
1686+ if branch == "b2" {
1687+ return fmt .Errorf ("conflict" )
1688+ }
1689+ return nil
1690+ }
1691+ mock .ConflictedFilesFn = func () ([]string , error ) { return nil , nil }
1692+
1693+ restore := git .SetOps (mock )
1694+ defer restore ()
1695+
1696+ cfg , _ , _ := config .NewTestConfig ()
1697+ cmd := RebaseCmd (cfg )
1698+ cmd .SetArgs ([]string {"--no-trunk" })
1699+ cmd .SetOut (io .Discard )
1700+ cmd .SetErr (io .Discard )
1701+ _ = cmd .Execute ()
1702+
1703+ // Load the saved state and verify the NoTrunk flag is persisted.
1704+ loaded , err := loadRebaseState (tmpDir )
1705+ require .NoError (t , err )
1706+ assert .True (t , loaded .NoTrunk ,
1707+ "saved rebase state should preserve NoTrunk flag" )
1708+ }
0 commit comments