Repository: git-town/git-town

Issue:
sync: pruning a freshly created branch causes bug
### Steps to reproduce

```
git hack test
git sync --prune
```

### Command output

```shell
~/git-town [main] > git hack kg-display-branch-types-config-option

[main] git fetch --prune --tags

[main] git checkout -b kg-display-branch-types-config-option
Switched to a new branch 'kg-display-branch-types-config-option'


branch "kg-display-branch-types-config-option" is now a child of "main"

branch "kg-display-branch-types-config-option" is now a feature branch


~/git-town [kg-display-branch-types-config-option] > git sync --prune

[kg-display-branch-types-config-option] git fetch --prune --tags

[kg-display-branch-types-config-option] git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

[main] git push origin :kg-display-branch-types-config-option
error: unable to delete 'kg-display-branch-types-config-option': remote ref does not exist
error: failed to push some refs to 'github.com:git-town/git-town.git'

[main] git branch -D kg-display-branch-types-config-option
Deleted branch kg-display-branch-types-config-option (was 1c8bbee9d).

[main] git push -u origin kg-display-branch-types-config-option
error: src refspec kg-display-branch-types-config-option does not match any
error: failed to push some refs to 'github.com:git-town/git-town.git'


deleted branch "kg-display-branch-types-config-option"

Error: exit status 1

To continue after having resolved conflicts, run "git town continue".
To go back to where you started, run "git town undo".
To continue by skipping the current branch, run "git town skip".
```

### Additional information

_No response_

Hints from issue comments:
@kevgo: This is now available in [v22.2](https://github.com/git-town/git-town/releases/tag/v22.2.0).

Solution patch:
diff --git a/features/sync/current_branch/feature_branch/merge_sync_strategy/prune/fresh_branch.feature b/features/sync/current_branch/feature_branch/merge_sync_strategy/prune/fresh_branch.feature
new file mode 100644
index 000000000..219bbd98a
--- /dev/null
+++ b/features/sync/current_branch/feature_branch/merge_sync_strategy/prune/fresh_branch.feature
@@ -0,0 +1,30 @@
+Feature: prune a freshly created branch
+
+  Background:
+    Given a Git repo with origin
+    And the branches
+      | NAME    | TYPE    | PARENT | LOCATIONS |
+      | feature | feature | main   | local     |
+    And the current branch is "feature"
+    When I run "git-town sync --prune"
+
+  Scenario: result
+    Then Git Town runs the commands
+      | BRANCH  | COMMAND                  |
+      | feature | git fetch --prune --tags |
+      |         | git checkout main        |
+      | main    | git push origin :feature |
+      |         | git branch -D feature    |
+    And the branches are now
+      | REPOSITORY    | BRANCHES |
+      | local, origin | main     |
+    And all branches are now synchronized
+
+  Scenario: undo
+    When I run "git-town undo"
+    Then Git Town runs the commands
+      | BRANCH | COMMAND                                       |
+      | main   | git branch feature {{ sha 'initial commit' }} |
+      |        | git checkout feature                          |
+    And the initial branches and lineage exist now
+    And the initial commits exist now
diff --git a/internal/cmd/sync/sync_branch.go b/internal/cmd/sync/sync_branch.go
index 46fb3a9c5..3a1456683 100644
--- a/internal/cmd/sync/sync_branch.go
+++ b/internal/cmd/sync/sync_branch.go
@@ -157,7 +157,7 @@ func localBranchProgram(args localBranchProgramArgs) {
 		isMainBranch := branchType == configdomain.BranchTypeMainBranch
 		switch {
 		case !args.branchInfo.HasTrackingBranch():
-			args.Program.Value.Add(&opcodes.BranchTrackingCreate{Branch: args.localName})
+			args.Program.Value.Add(&opcodes.BranchTrackingCreateIfLocalExists{Branch: args.localName})
 		case isMainBranch && args.Remotes.HasUpstream() && args.Config.NormalConfig.SyncUpstream.ShouldSyncUpstream():
 			args.Program.Value.Add(&opcodes.PushCurrentBranchIfNeeded{CurrentBranch: args.localName})
 		case isMainOrPerennialBranch && !shouldPushPerennialBranch(args.branchInfo.SyncStatus):
diff --git a/internal/vm/opcodes/all.go b/internal/vm/opcodes/all.go
index e315c4fe5..3223e0c4c 100644
--- a/internal/vm/opcodes/all.go
+++ b/internal/vm/opcodes/all.go
@@ -24,6 +24,7 @@ func All() []shared.Opcode {
 		&BranchRemoteSetToSHAIfNeeded{},
 		&BranchRemoteSetToSHA{},
 		&BranchReset{},
+		&BranchTrackingCreateIfLocalExists{},
 		&BranchTrackingCreate{},
 		&BranchTrackingDelete{},
 		&BranchTypeOverrideRemove{},
diff --git a/internal/vm/opcodes/branch_tracking_create_if_local_exists.go b/internal/vm/opcodes/branch_tracking_create_if_local_exists.go
new file mode 100644
index 000000000..40dc02b18
--- /dev/null
+++ b/internal/vm/opcodes/branch_tracking_create_if_local_exists.go
@@ -0,0 +1,20 @@
+package opcodes
+
+import (
+	"github.com/git-town/git-town/v22/internal/git/gitdomain"
+	"github.com/git-town/git-town/v22/internal/vm/shared"
+)
+
+// BranchTrackingCreateIfLocalExists pushes the given local branch up to origin
+// and marks it as tracking the current branch,
+// but only if the given branch still exists locally.
+type BranchTrackingCreateIfLocalExists struct {
+	Branch gitdomain.LocalBranchName
+}
+
+func (self *BranchTrackingCreateIfLocalExists) Run(args shared.RunArgs) error {
+	if args.Git.BranchExists(args.Backend, self.Branch) {
+		args.PrependOpcodes(&BranchTrackingCreate{Branch: self.Branch})
+	}
+	return nil
+}



Test patch:
diff --git a/internal/state/runstate/save_load_test.go b/internal/state/runstate/save_load_test.go
index b84724f88..2488eb9af 100644
--- a/internal/state/runstate/save_load_test.go
+++ b/internal/state/runstate/save_load_test.go
@@ -66,6 +66,7 @@ func TestLoadSave(t *testing.T) {
 				&opcodes.BranchRemoteSetToSHAIfNeeded{Branch: "branch", MustHaveSHA: "111111", SetToSHA: "222222"},
 				&opcodes.BranchReset{Target: "branch"},
 				&opcodes.BranchTrackingCreate{Branch: "branch"},
+				&opcodes.BranchTrackingCreateIfLocalExists{Branch: "branch"},
 				&opcodes.BranchTrackingDelete{Branch: "origin/branch"},
 				&opcodes.BranchTypeOverrideSet{Branch: "branch", BranchType: configdomain.BranchTypeFeatureBranch},
 				&opcodes.BranchTypeOverrideRemove{Branch: "branch"},
@@ -279,6 +280,12 @@ func TestLoadSave(t *testing.T) {
       },
       "type": "BranchTrackingCreate"
     },
+    {
+      "data": {
+        "Branch": "branch"
+      },
+      "type": "BranchTrackingCreateIfLocalExists"
+    },
     {
       "data": {
         "Branch": "origin/branch"