Repository: git-town/git-town

Issue:
propose: always detached
When I propose something, I want to propose the branch/stack as-is. The act of proposing should not pull in updates from the main branch. If I want that, I'll sync before proposing.

Discussion:
@kevgo: This is now available as part of [Git Town 20.2](https://github.com/git-town/git-town/releases/tag/v20.2.0).

Solution patch:
diff --git a/features/propose/branch_types/prototype.feature b/features/propose/branch_types/prototype.feature
index a1bd5fa37..1d6418020 100644
--- a/features/propose/branch_types/prototype.feature
+++ b/features/propose/branch_types/prototype.feature
@@ -21,10 +21,6 @@ Feature: Create proposals for prototype branches
       | BRANCH    | COMMAND                                                              |
       | prototype | git fetch --prune --tags                                             |
       | (none)    | Looking for proposal online ... ok                                   |
-      | prototype | git checkout main                                                    |
-      | main      | git -c rebase.updateRefs=false rebase origin/main                    |
-      |           | git push                                                             |
-      |           | git checkout prototype                                               |
       | prototype | git merge --no-edit --ff main                                        |
       |           | git push -u origin prototype                                         |
       | (none)    | open https://github.com/git-town/git-town/compare/prototype?expand=1 |
diff --git a/features/propose/stack/parent_shipped_remotely_and_deleted_locally.feature b/features/propose/stack/parent_shipped_remotely_and_deleted_locally.feature
index a8ab1f577..8e2552bb7 100644
--- a/features/propose/stack/parent_shipped_remotely_and_deleted_locally.feature
+++ b/features/propose/stack/parent_shipped_remotely_and_deleted_locally.feature
@@ -21,12 +21,8 @@ Feature: proposing a branch whose parent was shipped and the local branch delete
     Then Git Town runs the commands
       | BRANCH | COMMAND                                                          |
       | child  | git fetch --prune --tags                                         |
-      |        | git checkout main                                                |
-      | main   | git -c rebase.updateRefs=false rebase origin/main                |
-      |        | git checkout child                                               |
-      | child  | git merge --no-edit --ff main                                    |
+      |        | git merge --no-edit --ff main                                    |
       |        | git merge --no-edit --ff origin/child                            |
-      |        | git push                                                         |
       | (none) | open https://github.com/git-town/git-town/compare/child?expand=1 |
     And the branches are now
       | REPOSITORY    | BRANCHES    |
@@ -37,13 +33,7 @@ Feature: proposing a branch whose parent was shipped and the local branch delete
 
   Scenario: undo
     When I run "git-town undo"
-    Then Git Town runs the commands
-      | BRANCH | COMMAND                                         |
-      | child  | git reset --hard {{ sha 'child commit' }}       |
-      |        | git push --force-with-lease --force-if-includes |
-      |        | git checkout main                               |
-      | main   | git reset --hard {{ sha 'initial commit' }}     |
-      |        | git checkout child                              |
+    Then Git Town runs no commands
     And the branches are now
       | REPOSITORY    | BRANCHES    |
       | local, origin | main, child |
diff --git a/features/propose/sync_status/on_outdated_branch.feature b/features/propose/sync_status/on_outdated_branch.feature
index e435917e5..888ec03b4 100644
--- a/features/propose/sync_status/on_outdated_branch.feature
+++ b/features/propose/sync_status/on_outdated_branch.feature
@@ -26,10 +26,7 @@ Feature: sync before proposing
       | BRANCH | COMMAND                                                                   |
       | child  | git fetch --prune --tags                                                  |
       | (none) | Looking for proposal online ... ok                                        |
-      | child  | git checkout main                                                         |
-      | main   | git -c rebase.updateRefs=false rebase origin/main                         |
-      |        | git push                                                                  |
-      |        | git checkout parent                                                       |
+      | child  | git checkout parent                                                       |
       | parent | git merge --no-edit --ff main                                             |
       |        | git merge --no-edit --ff origin/parent                                    |
       |        | git push                                                                  |
@@ -40,8 +37,8 @@ Feature: sync before proposing
       | (none) | open https://github.com/git-town/git-town/compare/parent...child?expand=1 |
     And these commits exist now
       | BRANCH | LOCATION      | MESSAGE                                                  |
-      | main   | local, origin | origin main commit                                       |
-      |        |               | local main commit                                        |
+      | main   | local         | local main commit                                        |
+      |        | origin        | origin main commit                                       |
       | child  | local, origin | local child commit                                       |
       |        |               | Merge branch 'parent' into child                         |
       |        |               | origin child commit                                      |
@@ -50,3 +47,4 @@ Feature: sync before proposing
       |        |               | Merge branch 'main' into parent                          |
       |        |               | origin parent commit                                     |
       |        |               | Merge remote-tracking branch 'origin/parent' into parent |
+      |        | origin        | local main commit                                        |
diff --git a/internal/cmd/propose.go b/internal/cmd/propose.go
index 48ce7790e..4cba4e624 100644
--- a/internal/cmd/propose.go
+++ b/internal/cmd/propose.go
@@ -249,7 +249,7 @@ func determineProposeData(repo execute.OpenRepoResult, dryRun configdomain.DryRu
 		branchNamesToPropose = make(gitdomain.LocalBranchNames, len(branchNamesToSync))
 		copy(branchNamesToPropose, branchNamesToSync)
 	} else {
-		branchNamesToSync = validatedConfig.NormalConfig.Lineage.BranchAndAncestors(initialBranch)
+		branchNamesToSync = validatedConfig.NormalConfig.Lineage.BranchAndAncestorsWithoutRoot(initialBranch)
 		branchNamesToPropose = gitdomain.LocalBranchNames{initialBranch}
 		if err = validateBranchTypeToPropose(branchesAndTypes[initialBranch]); err != nil {
 			return data, false, err
diff --git a/internal/config/configdomain/lineage.go b/internal/config/configdomain/lineage.go
index f3307e20f..16f2a4e32 100644
--- a/internal/config/configdomain/lineage.go
+++ b/internal/config/configdomain/lineage.go
@@ -92,6 +92,12 @@ func (self Lineage) BranchAndAncestors(branchName gitdomain.LocalBranchName) git
 	return append(self.Ancestors(branchName), branchName)
 }
 
+// BranchAndAncestorsWithoutRoot provides the full ancestry for the branch with the given name,
+// including the branch, but without the perennial branch at the root.
+func (self Lineage) BranchAndAncestorsWithoutRoot(branchName gitdomain.LocalBranchName) gitdomain.LocalBranchNames {
+	return append(self.AncestorsWithoutRoot(branchName), branchName)
+}
+
 // BranchLineageWithoutRoot provides all branches in the lineage of the given branch,
 // from oldest to youngest, including the given branch.
 func (self Lineage) BranchLineageWithoutRoot(branch gitdomain.LocalBranchName, perennialBranches gitdomain.LocalBranchNames) gitdomain.LocalBranchNames {



Test patch:
diff --git a/internal/config/configdomain/lineage_test.go b/internal/config/configdomain/lineage_test.go
index 1fd5473b5..fac5c7776 100644
--- a/internal/config/configdomain/lineage_test.go
+++ b/internal/config/configdomain/lineage_test.go
@@ -92,6 +92,17 @@ func TestLineage(t *testing.T) {
 		must.Eq(t, want, have)
 	})
 
+	t.Run("BranchAndAncestorsWithoutRoot", func(t *testing.T) {
+		t.Parallel()
+		lineage := configdomain.NewLineageWith(configdomain.LineageData{
+			one: main,
+			two: one,
+		})
+		have := lineage.BranchAndAncestorsWithoutRoot(two)
+		want := gitdomain.LocalBranchNames{one, two}
+		must.Eq(t, want, have)
+	})
+
 	t.Run("BranchLineageWithoutRoot", func(t *testing.T) {
 		t.Parallel()
 		t.Run("only root exists", func(t *testing.T) {