Repository: git-town/git-town

Issue:
switch: specify branch sort order in config or argument
### Your message to us

Hey folks, thanks so much for the work on git town.  Its a joy to use.

I work in a couple places where we prefix branches with the date in YYYYMM format.  This causes the most recent branches to be at the bottom of `git town switch`.  I'm curious if a config option or argument could be added to invert the order `git town switch` presents branches.

I took a look into `internal/cmd/switch.go` and `internal/cli/dialog/switch_branch.go`-- its unclear to me whether the order could be inverted at time of presentation or if the Lineage source depends on the current order 😔 

Thanks again for the wonderful tool 👋 

Discussion:
@kevgo: A config option to control how entries are sorted in [git town switch](https://www.git-town.com/commands/switch.html) and [git town branch](https://www.git-town.com/commands/branch.html) sounds like a great idea!@kevgo: Possible name for the config option that reverses the order of displayed branches: `git-town.branch-order`. Possible options: `asc` (default) and `desc`.

Usage via Git metadata:

```
git config git-town.branches-order desc
```

In the config file:

```toml
[branches]
order = "desc"
```

As CLI flag:

```
git town switch --branches-order=desc
```@kevgo: On the other hand, everything with Git Town is around branches, so we could keep it short and omit "branches":

Config file:

```toml
[branches]
order = "desc"
```

Git config

```
git config git-town.order desc
```

CLI

```
git-town switch --order=desc
```
@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/branch/order.feature b/features/branch/order.feature
new file mode 100644
index 000000000..8bffe75c5
--- /dev/null
+++ b/features/branch/order.feature
@@ -0,0 +1,112 @@
+Feature: change the display order
+
+  Background:
+    Given a Git repo with origin
+    And the branches
+      | NAME       | TYPE    | PARENT  | LOCATIONS |
+      | 2024-03    | feature | main    | local     |
+      | 2025-03-01 | feature | 2024-03 | local     |
+      | 2025-03-02 | feature | 2024-03 | local     |
+      | 2025-06    | feature | main    | local     |
+      | 2025-06-01 | feature | 2025-06 | local     |
+      | 2025-06-02 | feature | 2025-06 | local     |
+      | 2025-06-03 | feature | 2025-06 | local     |
+    And the current branch is "2025-06"
+
+  Scenario: CLI ascending
+    When I run "git-town branch --order=asc"
+    Then Git Town runs no commands
+    And Git Town prints:
+      """
+        main
+          2024-03
+            2025-03-01
+            2025-03-02
+      *   2025-06
+            2025-06-01
+            2025-06-02
+            2025-06-03
+      """
+
+  Scenario: CLI descending
+    When I run "git-town branch --order=desc"
+    Then Git Town runs no commands
+    And Git Town prints:
+      """
+        main
+      *   2025-06
+            2025-06-03
+            2025-06-02
+            2025-06-01
+          2024-03
+            2025-03-02
+            2025-03-01
+      """
+
+  Scenario: global Git descending
+    Given global Git setting "git-town.order" is "desc"
+    When I run "git-town branch"
+    Then Git Town runs no commands
+    And Git Town prints:
+      """
+        main
+      *   2025-06
+            2025-06-03
+            2025-06-02
+            2025-06-01
+          2024-03
+            2025-03-02
+            2025-03-01
+      """
+
+  Scenario: local Git descending
+    Given local Git setting "git-town.order" is "desc"
+    When I run "git-town branch"
+    Then Git Town runs no commands
+    And Git Town prints:
+      """
+        main
+      *   2025-06
+            2025-06-03
+            2025-06-02
+            2025-06-01
+          2024-03
+            2025-03-02
+            2025-03-01
+      """
+
+  Scenario: environment variable descending
+    When I run "git-town branch" with these environment variables
+      | GIT_TOWN_ORDER | desc |
+    Then Git Town runs no commands
+    And Git Town prints:
+      """
+        main
+      *   2025-06
+            2025-06-03
+            2025-06-02
+            2025-06-01
+          2024-03
+            2025-03-02
+            2025-03-01
+      """
+
+  Scenario: config file descending
+    Given the configuration file:
+      """
+      [branches]
+      order = "desc"
+      """
+    When I run "git-town branch"
+    Then Git Town runs no commands
+    And Git Town prints:
+      """
+        main
+      *   2025-06
+            2025-06-03
+            2025-06-02
+            2025-06-01
+          2024-03
+            2025-03-02
+            2025-03-01
+      """
diff --git a/features/config/color.ui/always.feature b/features/config/color.ui/always.feature
index 692a617f6..0cdf04d7e 100644
--- a/features/config/color.ui/always.feature
+++ b/features/config/color.ui/always.feature
@@ -45,6 +45,7 @@ Feature: show the configuration
         perennial regex: ^release-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: asc
 
       Configuration:
         offline: no
@@ -134,6 +135,7 @@ Feature: show the configuration
         perennial regex: ^release-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: asc
 
       Configuration:
         offline: no
@@ -182,6 +184,7 @@ Feature: show the configuration
     And Git setting "git-town.observed-regex" is "^git-observed-regex"
     And Git setting "git-town.perennial-regex" is "^git-perennial-"
     And Git setting "git-town.feature-regex" is "git-feature-.*"
+    And Git setting "git-town.order" is "desc"
     And Git setting "git-town.share-new-branches" is "no"
     And Git setting "git-town.ship-strategy" is "squash-merge"
     And Git setting "git-town.ship-delete-tracking-branch" is "false"
@@ -204,6 +207,7 @@ Feature: show the configuration
       contribution-regex = "^config-contribution-regex"
       observed-regex = "^config-observed-regex"
       unknown-type = "contribution"
+      order = "asc"
 
       [create]
       share-new-branches = "push"
@@ -242,6 +246,7 @@ Feature: show the configuration
         perennial regex: ^git-perennial-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: desc
 
       Configuration:
         offline: no
@@ -306,6 +311,7 @@ Feature: show the configuration
         perennial regex: (not set)
         prototype branches: prototype-1, prototype-2
         unknown branch type: feature
+        order: asc
 
       Configuration:
         offline: no
@@ -376,6 +382,7 @@ Feature: show the configuration
         perennial regex: (not set)
         prototype branches: prototype-1, prototype-2
         unknown branch type: feature
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/config/view/alternative_config_file.feature b/features/config/view/alternative_config_file.feature
index b7e739ef6..74c11302b 100644
--- a/features/config/view/alternative_config_file.feature
+++ b/features/config/view/alternative_config_file.feature
@@ -23,6 +23,7 @@ Feature: show the configuration when using an alternative config file
         perennial regex: (not set)
         prototype branches: (none)
         unknown branch type: feature
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/config/view/config_file.feature b/features/config/view/config_file.feature
index 9f6859fa2..d24c1b3af 100644
--- a/features/config/view/config_file.feature
+++ b/features/config/view/config_file.feature
@@ -26,6 +26,7 @@ Feature: display information from config file
       contribution-regex = "^renovate/"
       observed-regex = "^dependabot/"
       unknown-type = "observed"
+      order = "desc"
 
       [create]
       share-new-branches = "push"
@@ -66,6 +67,7 @@ Feature: display information from config file
         perennial regex: ^release-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: desc
 
       Configuration:
         offline: no
diff --git a/features/config/view/detached_head.feature b/features/config/view/detached_head.feature
index 91caf2ad9..6291d805a 100644
--- a/features/config/view/detached_head.feature
+++ b/features/config/view/detached_head.feature
@@ -42,6 +42,7 @@ Feature: display configuration from Git metadata in detached head state
         perennial regex: ^release-
         prototype branches: (none)
         unknown branch type: observed
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/config/view/environment_variables.feature b/features/config/view/environment_variables.feature
index 088963403..4874f418b 100644
--- a/features/config/view/environment_variables.feature
+++ b/features/config/view/environment_variables.feature
@@ -30,6 +30,7 @@ Feature: display configuration defined in environment variables
       | GIT_TOWN_MAIN_BRANCH                 | dev                |
       | GIT_TOWN_NEW_BRANCH_TYPE             | prototype          |
       | GIT_TOWN_OBSERVED_REGEX              | ^dependabot/       |
+      | GIT_TOWN_ORDER                       | desc               |
       | GIT_TOWN_ORIGIN_HOSTNAME             | codeforge          |
       | GIT_TOWN_OFFLINE                     | 1                  |
       | GIT_TOWN_PERENNIAL_BRANCHES          | qa staging         |
@@ -60,6 +61,7 @@ Feature: display configuration defined in environment variables
         perennial regex: ^release-
         prototype branches: (none)
         unknown branch type: observed
+        order: desc
 
       Configuration:
         offline: yes
diff --git a/features/config/view/git_metadata.feature b/features/config/view/git_metadata.feature
index 8699728c0..571243228 100644
--- a/features/config/view/git_metadata.feature
+++ b/features/config/view/git_metadata.feature
@@ -42,6 +42,7 @@ Feature: display configuration from Git metadata
         perennial regex: ^release-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: asc
 
       Configuration:
         offline: no
@@ -105,6 +106,7 @@ Feature: display configuration from Git metadata
         perennial regex: (not set)
         prototype branches: prototype-1, prototype-2
         unknown branch type: feature
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/config/view/in_subfolder.feature b/features/config/view/in_subfolder.feature
index 97b45ee3a..1784dece5 100644
--- a/features/config/view/in_subfolder.feature
+++ b/features/config/view/in_subfolder.feature
@@ -24,6 +24,7 @@ Feature: show the configuration from a subfolder
         perennial regex: (not set)
         prototype branches: (none)
         unknown branch type: feature
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/config/view/lang/es.feature b/features/config/view/lang/es.feature
index 6ae1cd256..308be5907 100644
--- a/features/config/view/lang/es.feature
+++ b/features/config/view/lang/es.feature
@@ -39,6 +39,7 @@ Feature: show the configuration in Spanish
         perennial regex: ^release-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/config/view/multiple_config_sources.feature b/features/config/view/multiple_config_sources.feature
index 71b64077e..90ef8323d 100644
--- a/features/config/view/multiple_config_sources.feature
+++ b/features/config/view/multiple_config_sources.feature
@@ -26,6 +26,7 @@ Feature: show the configuration
     And Git setting "git-town.perennial-regex" is "^git-perennial-"
     And Git setting "git-town.feature-regex" is "git-feature-.*"
     And Git setting "git-town.github-connector" is "api"
+    And Git setting "git-town.order" is "desc"
     And Git setting "git-town.share-new-branches" is "no"
     And Git setting "git-town.ship-strategy" is "squash-merge"
     And Git setting "git-town.ship-delete-tracking-branch" is "false"
@@ -46,6 +47,7 @@ Feature: show the configuration
       contribution-regex = "^config-contribution-regex"
       observed-regex = "^config-observed-regex"
       unknown-type = "contribution"
+      order = "asc"
 
       [create]
       share-new-branches = "push"
@@ -84,6 +86,7 @@ Feature: show the configuration
         perennial regex: ^git-perennial-
         prototype branches: prototype-1, prototype-2
         unknown branch type: observed
+        order: desc
 
       Configuration:
         offline: no
diff --git a/features/config/view/no_config.feature b/features/config/view/no_config.feature
index 8996b934e..aaba431e0 100644
--- a/features/config/view/no_config.feature
+++ b/features/config/view/no_config.feature
@@ -26,6 +26,7 @@ Feature: no configuration data
         perennial regex: (not set)
         prototype branches: (none)
         unknown branch type: feature
+        order: asc
 
       Configuration:
         offline: no
diff --git a/features/init/change_git_metadata.feature b/features/init/change_git_metadata.feature
index 297baa1ff..adf983a17 100644
--- a/features/init/change_git_metadata.feature
+++ b/features/init/change_git_metadata.feature
@@ -10,6 +10,7 @@ Feature: change existing information in Git metadata
     And the main branch is "main"
     And local Git setting "git-town.new-branch-type" is "parked"
     And local Git setting "git-town.share-new-branches" is "no"
+    And local Git setting "git-town.order" is "desc"
     And local Git setting "git-town.push-branches" is "false"
     And local Git setting "git-town.push-hook" is "false"
     And local Git setting "git-town.auto-sync" is "false"
@@ -47,6 +48,7 @@ Feature: change existing information in Git metadata
       | push hook                   | down enter             |
       | ship strategy               | down down enter        |
       | ship delete tracking branch | down enter             |
+      | order                       | up enter               |
       | config storage              | enter                  |
 
   Scenario: result
@@ -82,6 +84,7 @@ Feature: change existing information in Git metadata
       | git config git-town.feature-regex feat                   |
       | git config git-town.contribution-regex cont              |
       | git config git-town.observed-regex obs                   |
+      | git config git-town.order asc                            |
       | git config git-town.push-branches true                   |
       | git config git-town.push-hook true                       |
       | git config git-town.share-new-branches push              |
diff --git a/features/init/migrate_git_to_file.feature b/features/init/migrate_git_to_file.feature
index 8667c7178..052c4f241 100644
--- a/features/init/migrate_git_to_file.feature
+++ b/features/init/migrate_git_to_file.feature
@@ -15,6 +15,7 @@ Feature: migrate existing configuration in Git metadata to a config file
     And local Git setting "git-town.push-branches" is "true"
     And local Git setting "git-town.push-hook" is "true"
     And local Git setting "git-town.new-branch-type" is "prototype"
+    And local Git setting "git-town.order" is "desc"
     And local Git setting "git-town.ship-strategy" is "squash-merge"
     And local Git setting "git-town.ship-delete-tracking-branch" is "false"
     And local Git setting "git-town.stash" is "false"
@@ -52,6 +53,7 @@ Feature: migrate existing configuration in Git metadata to a config file
       | push hook                   | enter      |
       | ship strategy               | enter      |
       | ship delete tracking branch | enter      |
+      | order                       | enter      |
       | config storage              | down enter |
 
   Scenario: result
@@ -64,6 +66,7 @@ Feature: migrate existing configuration in Git metadata to a config file
       | git config --unset git-town.main-branch                 |
       | git config --unset git-town.new-branch-type             |
       | git config --unset git-town.observed-regex              |
+      | git config --unset git-town.order                       |
       | git config --unset git-town.perennial-branches          |
       | git config --unset git-town.perennial-regex             |
       | git config --unset git-town.share-new-branches          |
@@ -88,6 +91,7 @@ Feature: migrate existing configuration in Git metadata to a config file
     And local Git setting "git-town.feature-regex" now doesn't exist
     And local Git setting "git-town.contribution-regex" now doesn't exist
     And local Git setting "git-town.observed-regex" now doesn't exist
+    And local Git setting "git-town.order" now doesn't exist
     And local Git setting "git-town.unknown-branch-type" now doesn't exist
     And local Git setting "git-town.share-new-branches" now doesn't exist
     And local Git setting "git-town.push-branches" now doesn't exist
@@ -102,6 +106,7 @@ Feature: migrate existing configuration in Git metadata to a config file
 
       [branches]
       main = "main"
+      order = "desc"
       perennials = ["qa"]
       perennial-regex = "release-.*"
 
@@ -139,6 +144,7 @@ Feature: migrate existing configuration in Git metadata to a config file
     And local Git setting "git-town.feature-regex" is now "user-.*"
     And local Git setting "git-town.contribution-regex" is now "coworker-.*"
     And local Git setting "git-town.observed-regex" is now "other-.*"
+    And local Git setting "git-town.order" is now "desc"
     And local Git setting "git-town.unknown-branch-type" is now "observed"
     And local Git setting "git-town.share-new-branches" is now "no"
     And local Git setting "git-town.push-branches" is now "true"
diff --git a/features/init/remove_git_metadata.feature b/features/init/remove_git_metadata.feature
index 3cac2b6b1..27e385f81 100644
--- a/features/init/remove_git_metadata.feature
+++ b/features/init/remove_git_metadata.feature
@@ -33,6 +33,7 @@ Feature: remove existing configuration in Git metadata
     And local Git setting "git-town.hosting-origin-hostname" is "code"
     And local Git setting "git-town.new-branch-type" is "parked"
     And local Git setting "git-town.observed-regex" is "obs.*"
+    And local Git setting "git-town.order" is "desc"
     And local Git setting "git-town.perennial-branches" is "qa"
     And local Git setting "git-town.perennial-regex" is "qa.*"
     And local Git setting "git-town.push-branches" is "false"
@@ -75,6 +76,7 @@ Feature: remove existing configuration in Git metadata
       | push hook                   | down enter                                                                  | enable              |
       | ship strategy               | down enter                                                                  |                     |
       | ship delete tracking branch | down enter                                                                  | disable             |
+      | order                       | up enter                                                                    |                     |
       | config storage              | enter                                                                       | git metadata        |
 
   Scenario: result
@@ -106,6 +108,7 @@ Feature: remove existing configuration in Git metadata
       | git config --unset git-town.feature-regex            |
       | git config --unset git-town.contribution-regex       |
       | git config --unset git-town.observed-regex           |
+      | git config git-town.order asc                        |
       | git config git-town.push-branches true               |
       | git config git-town.push-hook true                   |
       | git config git-town.share-new-branches no            |
@@ -131,6 +134,7 @@ Feature: remove existing configuration in Git metadata
     And local Git setting "git-town.auto-sync" is now "true"
     And local Git setting "git-town.dev-remote" is now "fork"
     And local Git setting "git-town.new-branch-type" is now "feature"
+    And local Git setting "git-town.order" is now "asc"
     And local Git setting "git-town.push-branches" is now "true"
     And local Git setting "git-town.push-hook" is now "true"
     And local Git setting "git-town.share-new-branches" is now "no"
@@ -173,6 +177,7 @@ Feature: remove existing configuration in Git metadata
     And local Git setting "git-town.hosting-origin-hostname" is now "code"
     And local Git setting "git-town.new-branch-type" is now "parked"
     And local Git setting "git-town.observed-regex" is now "obs.*"
+    And local Git setting "git-town.order" is now "desc"
     And local Git setting "git-town.perennial-regex" is now "qa.*"
     And local Git setting "git-town.push-branches" is now "false"
     And local Git setting "git-town.push-hook" is now "false"
diff --git a/features/init/unconfigured_with_complete_config_file.feature b/features/init/unconfigured_with_complete_config_file.feature
index 44563556d..b91182356 100644
--- a/features/init/unconfigured_with_complete_config_file.feature
+++ b/features/init/unconfigured_with_complete_config_file.feature
@@ -13,6 +13,7 @@ Feature: don't ask for information already provided by the config file
       perennial-regex = "release-"
       perennials = ["staging"]
       unknown-type = "observed"
+      order = "desc"
 
       [create]
       new-branch-type = "feature"
diff --git a/features/init/unconfigured_with_global_settings.feature b/features/init/unconfigured_with_global_settings.feature
index f5023be45..145cd8d06 100644
--- a/features/init/unconfigured_with_global_settings.feature
+++ b/features/init/unconfigured_with_global_settings.feature
@@ -15,6 +15,7 @@ Feature: setup a new repo when I have configured some things in global Git metad
     And global Git setting "git-town.new-branch-type" is "prototype"
     And global Git setting "git-town.unknown-branch-type" is "prototype"
     And global Git setting "git-town.hosting-origin-hostname" is "git"
+    And global Git setting "git-town.order" is "desc"
     And global Git setting "git-town.perennial-branches" is "perennials"
     And global Git setting "git-town.perennial-regex" is "^per-"
     And global Git setting "git-town.push-branches" is "false"
@@ -58,6 +59,7 @@ Feature: setup a new repo when I have configured some things in global Git metad
       | push hook                   | enter       |
       | ship strategy               | enter       |
       | ship delete tracking branch | enter       |
+      | order                       | enter       |
       | config storage              | enter       |
     Then Git Town runs the commands
       | COMMAND                                         |
diff --git a/features/init/unconfigured_with_partial_config_file.feature b/features/init/unconfigured_with_partial_config_file.feature
index 2ef0adb0e..57edab2ea 100644
--- a/features/init/unconfigured_with_partial_config_file.feature
+++ b/features/init/unconfigured_with_partial_config_file.feature
@@ -8,6 +8,7 @@ Feature: ask for information not provided by the config file
       [branches]
       main = "main"
       perennials = ["public"]
+      order = "desc"
 
       [hosting]
       dev-remote = "something"
diff --git a/internal/cli/dialog/lineage.go b/internal/cli/dialog/lineage.go
index 103665b3d..b971d5a0d 100644
--- a/internal/cli/dialog/lineage.go
+++ b/internal/cli/dialog/lineage.go
@@ -56,7 +56,7 @@ func Lineage(args LineageArgs) (additionalLineage configdomain.Lineage, addition
 		// ask for parent
 		excludeBranches := append(
 			gitdomain.LocalBranchNames{branchToVerify},
-			args.Config.NormalConfig.Lineage.Children(branchToVerify)...,
+			args.Config.NormalConfig.Lineage.Children(branchToVerify, args.Config.NormalConfig.Order)...,
 		)
 		noneEntry := SwitchBranchEntry{
 			Branch:        messages.SetParentNoneOption,
@@ -71,6 +71,7 @@ func Lineage(args LineageArgs) (additionalLineage configdomain.Lineage, addition
 			ExcludeBranches:   excludeBranches,
 			Lineage:           args.Config.NormalConfig.Lineage,
 			MainBranch:        Some(args.MainBranch),
+			Order:             args.Config.NormalConfig.Order,
 			Regexes:           []*regexp.Regexp{},
 			ShowAllBranches:   true,
 			UnknownBranchType: args.Config.NormalConfig.UnknownBranchType,
diff --git a/internal/cli/dialog/order.go b/internal/cli/dialog/order.go
new file mode 100644
index 000000000..bb96c0050
--- /dev/null
+++ b/internal/cli/dialog/order.go
@@ -0,0 +1,44 @@
+package dialog
+
+import (
+	"fmt"
+
+	"github.com/git-town/git-town/v22/internal/cli/dialog/dialogcomponents"
+	"github.com/git-town/git-town/v22/internal/cli/dialog/dialogcomponents/list"
+	"github.com/git-town/git-town/v22/internal/cli/dialog/dialogdomain"
+	"github.com/git-town/git-town/v22/internal/config/configdomain"
+	"github.com/git-town/git-town/v22/internal/messages"
+	. "github.com/git-town/git-town/v22/pkg/prelude"
+)
+
+const (
+	orderTitle = `Branch ordering`
+	orderHelp  = `
+How should Git Town order branches it displays?
+
+`
+)
+
+func Order(args Args[configdomain.Order]) (Option[configdomain.Order], dialogdomain.Exit, error) {
+	entries := list.Entries[Option[configdomain.Order]]{}
+	if global, hasGlobal := args.Global.Get(); hasGlobal {
+		entries = append(entries, list.Entry[Option[configdomain.Order]]{
+			Data: None[configdomain.Order](),
+			Text: fmt.Sprintf(messages.DialogUseGlobalValue, global),
+		})
+	}
+	entries = append(entries, list.Entries[Option[configdomain.Order]]{
+		{
+			Data: Some(configdomain.OrderAsc),
+			Text: "natural sorting (ascending)",
+		},
+		{
+			Data: Some(configdomain.OrderDesc),
+			Text: "natural sorting (descending)",
+		},
+	}...)
+	defaultPos := entries.IndexOf(args.Local)
+	selection, exit, err := dialogcomponents.RadioList(entries, defaultPos, orderTitle, orderHelp, args.Inputs, "order")
+	fmt.Printf(messages.Order, dialogcomponents.FormattedOption(selection, args.Global.IsSome(), exit))
+	return selection, exit, err
+}
diff --git a/internal/cli/dialog/switch_branch.go b/internal/cli/dialog/switch_branch.go
index 8550db129..a71a1cfe3 100644
--- a/internal/cli/dialog/switch_branch.go
+++ b/internal/cli/dialog/switch_branch.go
@@ -203,7 +203,12 @@ func (self SwitchModel) View() string {
 func NewSwitchBranchEntries(args NewSwitchBranchEntriesArgs) SwitchBranchEntries {
 	entries := make(SwitchBranchEntries, 0, args.Lineage.Len())
 	roots := args.Lineage.Roots()
-	slice.NaturalSort(roots)
+	switch args.Order {
+	case configdomain.OrderAsc:
+		slice.NaturalSort(roots)
+	case configdomain.OrderDesc:
+		slice.NaturalSortReverse(roots)
+	}
 	if mainBranch, hasMainBranch := args.MainBranch.Get(); hasMainBranch {
 		if !roots.Contains(mainBranch) {
 			roots = append(roots, mainBranch)
@@ -220,6 +225,7 @@ func NewSwitchBranchEntries(args NewSwitchBranchEntriesArgs) SwitchBranchEntries
 			excludeBranches:   args.ExcludeBranches,
 			indentation:       "",
 			lineage:           args.Lineage,
+			order:             args.Order,
 			regexes:           args.Regexes,
 			result:            &entries,
 			showAllBranches:   args.ShowAllBranches,
@@ -247,6 +253,7 @@ func NewSwitchBranchEntries(args NewSwitchBranchEntriesArgs) SwitchBranchEntries
 			excludeBranches:   args.ExcludeBranches,
 			indentation:       "",
 			lineage:           args.Lineage,
+			order:             args.Order,
 			regexes:           args.Regexes,
 			result:            &entries,
 			showAllBranches:   args.ShowAllBranches,
@@ -263,6 +270,7 @@ type NewSwitchBranchEntriesArgs struct {
 	ExcludeBranches   gitdomain.LocalBranchNames
 	Lineage           configdomain.Lineage
 	MainBranch        Option[gitdomain.LocalBranchName]
+	Order             configdomain.Order
 	Regexes           []*regexp.Regexp
 	ShowAllBranches   configdomain.AllBranches
 	UnknownBranchType configdomain.UnknownBranchType
@@ -341,7 +349,7 @@ func layoutBranches(args layoutBranchesArgs) {
 			})
 		}
 	}
-	for _, child := range args.lineage.Children(args.branch) {
+	for _, child := range args.lineage.Children(args.branch, args.order) {
 		layoutBranches(layoutBranchesArgs{
 			branch:            child,
 			branchInfos:       args.branchInfos,
@@ -350,6 +358,7 @@ func layoutBranches(args layoutBranchesArgs) {
 			excludeBranches:   args.excludeBranches,
 			indentation:       args.indentation + "  ",
 			lineage:           args.lineage,
+			order:             args.order,
 			regexes:           args.regexes,
 			result:            args.result,
 			showAllBranches:   args.showAllBranches,
@@ -366,6 +375,7 @@ type layoutBranchesArgs struct {
 	excludeBranches   gitdomain.LocalBranchNames
 	indentation       string
 	lineage           configdomain.Lineage
+	order             configdomain.Order
 	regexes           regexes.Regexes
 	result            *SwitchBranchEntries
 	showAllBranches   configdomain.AllBranches
diff --git a/internal/cli/flags/order.go b/internal/cli/flags/order.go
new file mode 100644
index 000000000..ac24bf085
--- /dev/null
+++ b/internal/cli/flags/order.go
@@ -0,0 +1,27 @@
+package flags
+
+import (
+	"cmp"
+
+	"github.com/git-town/git-town/v22/internal/config/configdomain"
+	. "github.com/git-town/git-town/v22/pkg/prelude"
+	"github.com/spf13/cobra"
+)
+
+const orderLong = "order"
+
+// type-safe access to the CLI arguments of type configdomain.Order
+func Order() (AddFunc, ReadOrderFlagFunc) {
+	addFlag := func(cmd *cobra.Command) {
+		cmd.Flags().StringP(orderLong, "o", "", "sort order for branch list (asc or desc)")
+	}
+	readFlag := func(cmd *cobra.Command) (Option[configdomain.Order], error) {
+		text, errFlag := cmd.Flags().GetString(orderLong)
+		order, errParse := configdomain.ParseOrder(text, "--order flag")
+		return order, cmp.Or(errFlag, errParse)
+	}
+	return addFlag, readFlag
+}
+
+// ReadOrderFlagFunc is the type signature for the function that reads the "order" flag from the args to the given Cobra command.
+type ReadOrderFlagFunc func(*cobra.Command) (Option[configdomain.Order], error)
diff --git a/internal/cli/format/branch_lineage.go b/internal/cli/format/branch_lineage.go
index 70bf73b71..c932b4d46 100644
--- a/internal/cli/format/branch_lineage.go
+++ b/internal/cli/format/branch_lineage.go
@@ -11,7 +11,7 @@ import (
 const indent = "  "
 
 // BranchLineage provides printable formatting of the given branch lineage.
-func BranchLineage(lineage configdomain.Lineage) string {
+func BranchLineage(lineage configdomain.Lineage, order configdomain.Order) string {
 	roots := lineage.Roots()
 	if len(roots) == 0 {
 		return ""
@@ -24,6 +24,7 @@ func BranchLineage(lineage configdomain.Lineage) string {
 			builder:     NewMutable(&result),
 			indentLevel: 0,
 			lineage:     lineage,
+			order:       order,
 		})
 	}
 	return result.String()[2:]
@@ -34,6 +35,7 @@ type branchTreeArgs struct {
 	builder     Mutable[strings.Builder]
 	indentLevel int
 	lineage     configdomain.Lineage
+	order       configdomain.Order
 }
 
 // branchTree provids a printable version of the given branch tree.
@@ -42,13 +44,14 @@ func branchTree(args branchTreeArgs) {
 		args.builder.Value.WriteString(indent)
 	}
 	args.builder.Value.WriteString(args.branch.String())
-	for _, child := range args.lineage.Children(args.branch) {
+	for _, child := range args.lineage.Children(args.branch, args.order) {
 		args.builder.Value.WriteString("\n")
 		branchTree(branchTreeArgs{
 			branch:      child,
 			builder:     args.builder,
 			indentLevel: args.indentLevel + 1,
 			lineage:     args.lineage,
+			order:       args.order,
 		})
 	}
 }
diff --git a/internal/cmd/append.go b/internal/cmd/append.go
index 2e1cb260f..508b61b1e 100644
--- a/internal/cmd/append.go
+++ b/internal/cmd/append.go
@@ -105,6 +105,7 @@ func appendCmd() *cobra.Command {
 				AutoSync:     sync,
 				Detached:     detached,
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: push,
 				Stash:        stash,
 				Verbose:      verbose,
@@ -405,7 +406,7 @@ type determineAppendDataArgs struct {
 
 func appendProgram(frontend subshelldomain.Runner, data appendFeatureData, finalMessages stringslice.Collector, beamCherryPick bool) program.Program {
 	prog := NewMutable(&program.Program{})
-	data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, finalMessages, frontend)
+	data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, finalMessages, frontend, data.config.NormalConfig.Order)
 	if !data.hasOpenChanges && !data.beam.ShouldBeam() && !data.commit.ShouldCommit() && data.config.NormalConfig.AutoSync.ShouldSync() {
 		branchesToDelete := set.New[gitdomain.LocalBranchName]()
 		sync.BranchesProgram(data.branchesToSync, sync.BranchProgramArgs{
diff --git a/internal/cmd/branch.go b/internal/cmd/branch.go
index d8d4bc042..14082248c 100644
--- a/internal/cmd/branch.go
+++ b/internal/cmd/branch.go
@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"cmp"
 	"fmt"
 	"os"
 	"regexp"
@@ -28,6 +29,7 @@ Git Town's equivalent of the "git branch" command.`
 )
 
 func branchCmd() *cobra.Command {
+	addOrderFlag, readOrderFlag := flags.Order()
 	addVerboseFlag, readVerboseFlag := flags.Verbose()
 	cmd := cobra.Command{
 		Use:   "branch",
@@ -35,8 +37,9 @@ func branchCmd() *cobra.Command {
 		Short: branchDesc,
 		Long:  cmdhelpers.Long(branchDesc, branchHelp),
 		RunE: func(cmd *cobra.Command, _ []string) error {
-			verbose, err := readVerboseFlag(cmd)
-			if err != nil {
+			order, errOrder := readOrderFlag(cmd)
+			verbose, errVerbose := readVerboseFlag(cmd)
+			if err := cmp.Or(errOrder, errVerbose); err != nil {
 				return err
 			}
 			cliConfig := cliconfig.New(cliconfig.NewArgs{
@@ -44,6 +47,7 @@ func branchCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        order,
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -51,6 +55,7 @@ func branchCmd() *cobra.Command {
 			return executeBranch(cliConfig)
 		},
 	}
+	addOrderFlag(&cmd)
 	addVerboseFlag(&cmd)
 	return &cmd
 }
@@ -85,6 +90,7 @@ Start:
 		ExcludeBranches:   gitdomain.LocalBranchNames{},
 		Lineage:           repo.UnvalidatedConfig.NormalConfig.Lineage,
 		MainBranch:        repo.UnvalidatedConfig.UnvalidatedConfig.MainBranch,
+		Order:             repo.UnvalidatedConfig.NormalConfig.Order,
 		Regexes:           []*regexp.Regexp{},
 		ShowAllBranches:   false,
 		UnknownBranchType: repo.UnvalidatedConfig.NormalConfig.UnknownBranchType,
diff --git a/internal/cmd/compress.go b/internal/cmd/compress.go
index 1c028c7f0..a05e67f9f 100644
--- a/internal/cmd/compress.go
+++ b/internal/cmd/compress.go
@@ -85,6 +85,7 @@ func compressCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -268,7 +269,7 @@ func determineCompressData(repo execute.OpenRepoResult, message Option[gitdomain
 	perennialBranches := branchesAndTypes.BranchesOfTypes(configdomain.BranchTypePerennialBranch, configdomain.BranchTypeMainBranch)
 	var branchNamesToCompress gitdomain.LocalBranchNames
 	if compressEntireStack {
-		branchNamesToCompress = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialBranches)
+		branchNamesToCompress = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialBranches, validatedConfig.NormalConfig.Order)
 	} else {
 		branchNamesToCompress = gitdomain.LocalBranchNames{initialBranch}
 	}
diff --git a/internal/cmd/config/get_parent.go b/internal/cmd/config/get_parent.go
index f8ab87804..b91365f41 100644
--- a/internal/cmd/config/get_parent.go
+++ b/internal/cmd/config/get_parent.go
@@ -33,6 +33,7 @@ func getParentCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/config/remove.go b/internal/cmd/config/remove.go
index 1f082e2bc..fa0f8c01e 100644
--- a/internal/cmd/config/remove.go
+++ b/internal/cmd/config/remove.go
@@ -35,6 +35,7 @@ func removeConfigCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/config/root.go b/internal/cmd/config/root.go
index aafa92785..7fdc877e2 100644
--- a/internal/cmd/config/root.go
+++ b/internal/cmd/config/root.go
@@ -36,6 +36,7 @@ func RootCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -78,6 +79,7 @@ func printConfig(config config.UnvalidatedConfig) {
 	print.Entry("perennial regex", format.OptionalStringerSetting(config.NormalConfig.PerennialRegex))
 	print.Entry("prototype branches", format.BranchNames(config.NormalConfig.PartialBranchesOfType(configdomain.BranchTypePrototypeBranch)))
 	print.Entry("unknown branch type", config.NormalConfig.UnknownBranchType.String())
+	print.Entry("order", config.NormalConfig.Order.String())
 	fmt.Println()
 	print.Header("Configuration")
 	print.Entry("offline", format.Bool(config.NormalConfig.Offline.IsOffline()))
@@ -118,6 +120,6 @@ func printConfig(config config.UnvalidatedConfig) {
 	print.Entry("auto-resolve phantom conflicts", format.Bool(config.NormalConfig.AutoResolve.ShouldAutoResolve()))
 	fmt.Println()
 	if config.NormalConfig.Lineage.Len() > 0 {
-		print.LabelAndValue("Branch Lineage", format.BranchLineage(config.NormalConfig.Lineage))
+		print.LabelAndValue("Branch Lineage", format.BranchLineage(config.NormalConfig.Lineage, config.NormalConfig.Order))
 	}
 }
diff --git a/internal/cmd/continue.go b/internal/cmd/continue.go
index f5746972e..eaa2429e7 100644
--- a/internal/cmd/continue.go
+++ b/internal/cmd/continue.go
@@ -46,6 +46,7 @@ func continueCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/contribute.go b/internal/cmd/contribute.go
index f83fe18e7..7aac339fd 100644
--- a/internal/cmd/contribute.go
+++ b/internal/cmd/contribute.go
@@ -55,6 +55,7 @@ func contributeCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/delete.go b/internal/cmd/delete.go
index 0b3bf53b3..9ad597bb9 100644
--- a/internal/cmd/delete.go
+++ b/internal/cmd/delete.go
@@ -82,6 +82,7 @@ func deleteCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -285,6 +286,7 @@ func determineDeleteData(args []string, repo execute.OpenRepoResult) (data delet
 		Offline:                    repo.IsOffline,
 		OldBranch:                  branchToDelete,
 		OldBranchHasTrackingBranch: branchToDeleteInfo.HasTrackingBranch(),
+		Order:                      validatedConfig.NormalConfig.Order,
 	})
 	lineageBranches := validatedConfig.NormalConfig.Lineage.BranchNames()
 	_, nonExistingBranches := branchesSnapshot.Branches.Select(repo.UnvalidatedConfig.NormalConfig.DevRemote, lineageBranches...)
@@ -308,7 +310,7 @@ func determineDeleteData(args []string, repo execute.OpenRepoResult) (data delet
 
 func deleteProgram(repo execute.OpenRepoResult, data deleteData, finalMessages stringslice.Collector) (runProgram, finalUndoProgram program.Program) {
 	prog := NewMutable(&program.Program{})
-	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Backend)
+	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Backend, data.config.NormalConfig.Order)
 	undoProg := NewMutable(&program.Program{})
 	switch data.branchToDeleteType {
 	case
@@ -356,6 +358,7 @@ func deleteFeatureBranch(prog, finalUndoProgram Mutable[program.Program], data d
 					CurrentBranch:            data.initialBranch,
 					Lineage:                  data.config.NormalConfig.Lineage,
 					MainAndPerennialBranches: data.config.MainAndPerennials(),
+					Order:                    data.config.NormalConfig.Order,
 				})
 				if err != nil {
 					fmt.Printf("failed to update proposal stack lineage: %s\n", err.Error())
@@ -399,7 +402,7 @@ func deleteLocalBranch(prog, finalUndoProgram Mutable[program.Program], data del
 		}
 		// delete the commits of this branch from all descendents
 		if data.config.NormalConfig.SyncFeatureStrategy == configdomain.SyncFeatureStrategyRebase {
-			descendents := data.config.NormalConfig.Lineage.Descendants(localBranchToDelete)
+			descendents := data.config.NormalConfig.Lineage.Descendants(localBranchToDelete, data.config.NormalConfig.Order)
 			for _, descendent := range descendents {
 				if branchInfo, hasBranchInfo := data.branchesSnapshot.Branches.FindByLocalName(descendent).Get(); hasBranchInfo {
 					parent := data.config.NormalConfig.Lineage.Parent(descendent).GetOr(data.config.ValidatedConfigData.MainBranch)
@@ -424,6 +427,7 @@ func deleteLocalBranch(prog, finalUndoProgram Mutable[program.Program], data del
 			sync.RemoveBranchConfiguration(sync.RemoveBranchConfigurationArgs{
 				Branch:  localBranchToDelete,
 				Lineage: data.config.NormalConfig.Lineage,
+				Order:   data.config.NormalConfig.Order,
 				Program: prog,
 			})
 		}
diff --git a/internal/cmd/detach.go b/internal/cmd/detach.go
index c19f65bed..b92240fbf 100644
--- a/internal/cmd/detach.go
+++ b/internal/cmd/detach.go
@@ -86,6 +86,7 @@ func detachCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -291,7 +292,7 @@ func determineDetachData(repo execute.OpenRepoResult) (data detachData, flow con
 	if branchHasMergeCommits {
 		return data, configdomain.ProgramFlowExit, fmt.Errorf(messages.BranchContainsMergeCommits, branchNameToDetach)
 	}
-	childBranches := validatedConfig.NormalConfig.Lineage.Children(branchNameToDetach)
+	childBranches := validatedConfig.NormalConfig.Lineage.Children(branchNameToDetach, validatedConfig.NormalConfig.Order)
 	children := make([]detachChildBranch, len(childBranches))
 	for c, childBranch := range childBranches {
 		proposal := None[forgedomain.Proposal]()
@@ -313,7 +314,7 @@ func determineDetachData(repo execute.OpenRepoResult) (data detachData, flow con
 			proposal: proposal,
 		}
 	}
-	descendentNames := validatedConfig.NormalConfig.Lineage.Descendants(branchNameToDetach)
+	descendentNames := validatedConfig.NormalConfig.Lineage.Descendants(branchNameToDetach, validatedConfig.NormalConfig.Order)
 	descendents := make([]detachChildBranch, len(descendentNames))
 	for d, descendentName := range descendentNames {
 		info, has := branchesSnapshot.Branches.FindByLocalName(descendentName).Get()
@@ -350,7 +351,7 @@ func determineDetachData(repo execute.OpenRepoResult) (data detachData, flow con
 
 func detachProgram(repo execute.OpenRepoResult, data detachData, finalMessages stringslice.Collector) program.Program {
 	prog := NewMutable(&program.Program{})
-	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Frontend)
+	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Frontend, data.config.NormalConfig.Order)
 	// step 1: delete the commits of the branch to detach from all descendents,
 	// while that branch is still in the form it had inside the stack
 	lastParent := data.parentBranch
@@ -392,7 +393,7 @@ func detachProgram(repo execute.OpenRepoResult, data detachData, finalMessages s
 				Parent: data.config.ValidatedConfigData.MainBranch,
 			},
 		)
-		for _, child := range data.config.NormalConfig.Lineage.Children(data.branchToDetachName) {
+		for _, child := range data.config.NormalConfig.Lineage.Children(data.branchToDetachName, data.config.NormalConfig.Order) {
 			prog.Value.Add(
 				&opcodes.LineageParentSet{
 					Branch: child,
diff --git a/internal/cmd/diff_parent.go b/internal/cmd/diff_parent.go
index 03c8e2d96..390a9c47b 100644
--- a/internal/cmd/diff_parent.go
+++ b/internal/cmd/diff_parent.go
@@ -47,6 +47,7 @@ func diffParentCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/down.go b/internal/cmd/down.go
index 61922d95a..9b34eb13b 100644
--- a/internal/cmd/down.go
+++ b/internal/cmd/down.go
@@ -43,6 +43,7 @@ func downCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -117,6 +118,7 @@ Start:
 		ExcludeBranches:   gitdomain.LocalBranchNames{},
 		Lineage:           repo.UnvalidatedConfig.NormalConfig.Lineage,
 		MainBranch:        repo.UnvalidatedConfig.UnvalidatedConfig.MainBranch,
+		Order:             repo.UnvalidatedConfig.NormalConfig.Order,
 		Regexes:           []*regexp.Regexp{},
 		ShowAllBranches:   false,
 		UnknownBranchType: repo.UnvalidatedConfig.NormalConfig.UnknownBranchType,
diff --git a/internal/cmd/feature.go b/internal/cmd/feature.go
index a8ceca963..fd24246dd 100644
--- a/internal/cmd/feature.go
+++ b/internal/cmd/feature.go
@@ -47,6 +47,7 @@ func featureCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/hack.go b/internal/cmd/hack.go
index c32685da8..cd8e3c6b6 100644
--- a/internal/cmd/hack.go
+++ b/internal/cmd/hack.go
@@ -98,6 +98,7 @@ func hackCmd() *cobra.Command {
 				AutoSync:     sync,
 				Detached:     detached,
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        stash,
 				Verbose:      verbose,
@@ -320,7 +321,7 @@ func determineHackData(args hackArgs, repo execute.OpenRepoResult) (data appendF
 		// ask the user for the parent branch
 		excludeBranches := append(
 			gitdomain.LocalBranchNames{initialBranch},
-			validatedConfig.NormalConfig.Lineage.Children(initialBranch)...,
+			validatedConfig.NormalConfig.Lineage.Children(initialBranch, validatedConfig.NormalConfig.Order)...,
 		)
 		noneEntry := dialog.SwitchBranchEntry{
 			Branch:        messages.SetParentNoneOption,
@@ -335,6 +336,7 @@ func determineHackData(args hackArgs, repo execute.OpenRepoResult) (data appendF
 			ExcludeBranches:   excludeBranches,
 			Lineage:           validatedConfig.NormalConfig.Lineage,
 			MainBranch:        Some(validatedConfig.ValidatedConfigData.MainBranch),
+			Order:             validatedConfig.NormalConfig.Order,
 			Regexes:           []*regexp.Regexp{},
 			ShowAllBranches:   true,
 			UnknownBranchType: validatedConfig.NormalConfig.UnknownBranchType,
diff --git a/internal/cmd/init.go b/internal/cmd/init.go
index e028e9911..c0e6b3d10 100644
--- a/internal/cmd/init.go
+++ b/internal/cmd/init.go
@@ -41,6 +41,7 @@ func initCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/merge.go b/internal/cmd/merge.go
index 996a886c7..0db41fe20 100644
--- a/internal/cmd/merge.go
+++ b/internal/cmd/merge.go
@@ -82,6 +82,7 @@ func mergeCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -376,7 +377,7 @@ func validateMergeData(repo execute.OpenRepoResult, data mergeData) error {
 	case gitdomain.SyncStatusOtherWorktree:
 		return fmt.Errorf(messages.BranchOtherWorktree, data.parentBranch)
 	}
-	children := data.config.NormalConfig.Lineage.Children(data.parentBranch)
+	children := data.config.NormalConfig.Lineage.Children(data.parentBranch, data.config.NormalConfig.Order)
 	if len(children) > 1 {
 		return fmt.Errorf("branch %q has more than one child", data.parentBranch)
 	}
diff --git a/internal/cmd/observe.go b/internal/cmd/observe.go
index edc045e5b..cbf7fe849 100644
--- a/internal/cmd/observe.go
+++ b/internal/cmd/observe.go
@@ -54,6 +54,7 @@ func observeCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/offline.go b/internal/cmd/offline.go
index be3cc0fe5..b4ecb7edf 100644
--- a/internal/cmd/offline.go
+++ b/internal/cmd/offline.go
@@ -44,6 +44,7 @@ func offlineCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/park.go b/internal/cmd/park.go
index 293e244a1..385e654be 100644
--- a/internal/cmd/park.go
+++ b/internal/cmd/park.go
@@ -48,6 +48,7 @@ func parkCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/prepend.go b/internal/cmd/prepend.go
index 323476de4..8710251e0 100644
--- a/internal/cmd/prepend.go
+++ b/internal/cmd/prepend.go
@@ -114,6 +114,7 @@ func prependCommand() *cobra.Command {
 				AutoSync:     sync,
 				Detached:     detached,
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: push,
 				Stash:        stash,
 				Verbose:      verbose,
@@ -424,7 +425,7 @@ func determinePrependData(args prependArgs, repo execute.OpenRepoResult) (data p
 func prependProgram(repo execute.OpenRepoResult, data prependData, finalMessages stringslice.Collector) program.Program {
 	prog := NewMutable(&program.Program{})
 	if !data.hasOpenChanges && !data.beam.ShouldBeam() && !data.commit.ShouldCommit() && data.config.NormalConfig.AutoSync.ShouldSync() {
-		data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, finalMessages, repo.Backend)
+		data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, finalMessages, repo.Backend, data.config.NormalConfig.Order)
 		branchesToDelete := set.New[gitdomain.LocalBranchName]()
 		sync.BranchesProgram(data.branchesToSync, sync.BranchProgramArgs{
 			BranchInfos:         data.branchInfos,
@@ -516,6 +517,7 @@ func prependProgram(repo execute.OpenRepoResult, data prependData, finalMessages
 					CurrentBranch:            data.initialBranch,
 					Lineage:                  data.config.NormalConfig.Lineage,
 					MainAndPerennialBranches: data.config.MainAndPerennials(),
+					Order:                    data.config.NormalConfig.Order,
 				})
 				if err != nil {
 					fmt.Printf("failed to update proposal stack lineage: %s\n", err.Error())
diff --git a/internal/cmd/propose.go b/internal/cmd/propose.go
index 4f1484d84..357f34ac8 100644
--- a/internal/cmd/propose.go
+++ b/internal/cmd/propose.go
@@ -83,6 +83,7 @@ func proposeCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -289,7 +290,7 @@ func determineProposeData(repo execute.OpenRepoResult, args proposeArgs) (data p
 	var branchNamesToPropose gitdomain.LocalBranchNames
 	var branchNamesToSync gitdomain.LocalBranchNames
 	if args.stack {
-		branchNamesToSync = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialAndMain)
+		branchNamesToSync = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialAndMain, validatedConfig.NormalConfig.Order)
 		branchNamesToPropose = make(gitdomain.LocalBranchNames, len(branchNamesToSync))
 		copy(branchNamesToPropose, branchNamesToSync)
 	} else {
@@ -365,7 +366,7 @@ func determineProposeData(repo execute.OpenRepoResult, args proposeArgs) (data p
 
 func proposeProgram(repo execute.OpenRepoResult, data proposeData) program.Program {
 	prog := NewMutable(&program.Program{})
-	data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, repo.FinalMessages, repo.Backend)
+	data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, repo.FinalMessages, repo.Backend, data.config.NormalConfig.Order)
 	branchesToDelete := set.New[gitdomain.LocalBranchName]()
 	sync.BranchesProgram(data.branchesToSync, sync.BranchProgramArgs{
 		BranchInfos:         data.branchInfos,
diff --git a/internal/cmd/prototype.go b/internal/cmd/prototype.go
index 89252badf..fbeb75bf7 100644
--- a/internal/cmd/prototype.go
+++ b/internal/cmd/prototype.go
@@ -51,6 +51,7 @@ func prototypeCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/rename.go b/internal/cmd/rename.go
index 14925fbb7..6cfcc0766 100644
--- a/internal/cmd/rename.go
+++ b/internal/cmd/rename.go
@@ -60,6 +60,7 @@ func renameCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -276,6 +277,7 @@ func determineRenameData(args []string, force configdomain.Force, repo execute.O
 		Offline:                    false,
 		OldBranch:                  oldBranchName,
 		OldBranchHasTrackingBranch: oldBranch.HasTrackingBranch(),
+		Order:                      validatedConfig.NormalConfig.Order,
 	})
 	return renameData{
 		branchInfosLastRun:       branchInfosLastRun,
@@ -297,7 +299,7 @@ func determineRenameData(args []string, force configdomain.Force, repo execute.O
 
 func renameProgram(repo execute.OpenRepoResult, data renameData, finalMessages stringslice.Collector) program.Program {
 	prog := NewMutable(&program.Program{})
-	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Backend)
+	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Backend, data.config.NormalConfig.Order)
 	oldLocalBranch, hasOldLocalBranch := data.oldBranch.LocalName.Get()
 	if !hasOldLocalBranch {
 		return prog.Immutable()
@@ -323,7 +325,7 @@ func renameProgram(repo execute.OpenRepoResult, data renameData, finalMessages s
 		}
 		prog.Value.Add(&opcodes.LineageParentRemove{Branch: oldLocalBranch})
 	}
-	for _, child := range data.config.NormalConfig.Lineage.Children(oldLocalBranch) {
+	for _, child := range data.config.NormalConfig.Lineage.Children(oldLocalBranch, data.config.NormalConfig.Order) {
 		prog.Value.Add(&opcodes.LineageParentSet{Branch: child, Parent: data.newBranch})
 	}
 	if oldTrackingBranch, hasOldTrackingBranch := data.oldBranch.RemoteName.Get(); hasOldTrackingBranch {
diff --git a/internal/cmd/repo.go b/internal/cmd/repo.go
index 4f4536dd4..0bf300550 100644
--- a/internal/cmd/repo.go
+++ b/internal/cmd/repo.go
@@ -48,6 +48,7 @@ func repoCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/runlog.go b/internal/cmd/runlog.go
index 0f493a7ca..44634da07 100644
--- a/internal/cmd/runlog.go
+++ b/internal/cmd/runlog.go
@@ -48,6 +48,7 @@ func runLogCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/set_parent.go b/internal/cmd/set_parent.go
index 03a46c2c0..a97322f3c 100644
--- a/internal/cmd/set_parent.go
+++ b/internal/cmd/set_parent.go
@@ -83,6 +83,7 @@ func setParentCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -132,7 +133,7 @@ Start:
 			// TODO: extract this logic into an "enterParent" function
 			excludeBranches := append(
 				gitdomain.LocalBranchNames{data.initialBranch},
-				data.config.NormalConfig.Lineage.Children(data.initialBranch)...,
+				data.config.NormalConfig.Lineage.Children(data.initialBranch, data.config.NormalConfig.Order)...,
 			)
 			noneEntry := dialog.SwitchBranchEntry{
 				Branch:        messages.SetParentNoneOption,
@@ -147,6 +148,7 @@ Start:
 				ExcludeBranches:   excludeBranches,
 				Lineage:           repo.UnvalidatedConfig.NormalConfig.Lineage,
 				MainBranch:        repo.UnvalidatedConfig.UnvalidatedConfig.MainBranch,
+				Order:             data.config.NormalConfig.Order,
 				Regexes:           []*regexp.Regexp{},
 				ShowAllBranches:   false,
 				UnknownBranchType: repo.UnvalidatedConfig.NormalConfig.UnknownBranchType,
@@ -399,7 +401,7 @@ func setParentProgram(newParentOpt Option[gitdomain.LocalBranchName], data setPa
 			}
 			// remove commits from descendents
 			if hasParent {
-				descendents := data.config.NormalConfig.Lineage.Descendants(data.initialBranch)
+				descendents := data.config.NormalConfig.Lineage.Descendants(data.initialBranch, data.config.NormalConfig.Order)
 				for _, descendent := range descendents {
 					prog.Add(
 						&opcodes.CheckoutIfNeeded{
@@ -457,6 +459,7 @@ func updateProposalLineage(prog *program.Program, newParentOpt Option[gitdomain.
 		CurrentBranch:            data.initialBranch,
 		Lineage:                  data.config.NormalConfig.Lineage,
 		MainAndPerennialBranches: data.config.MainAndPerennials(),
+		Order:                    data.config.NormalConfig.Order,
 	})
 	if err != nil {
 		fmt.Printf("failed to update proposal stack lineage: %s\n", err.Error())
@@ -481,6 +484,7 @@ func updateProposalLineage(prog *program.Program, newParentOpt Option[gitdomain.
 			CurrentBranch:            newParent,
 			Lineage:                  data.config.NormalConfig.Lineage,
 			MainAndPerennialBranches: data.config.MainAndPerennials(),
+			Order:                    data.config.NormalConfig.Order,
 		})
 
 		if err == nil {
diff --git a/internal/cmd/ship/cmd.go b/internal/cmd/ship/cmd.go
index 4b3aacade..ff2fa4eef 100644
--- a/internal/cmd/ship/cmd.go
+++ b/internal/cmd/ship/cmd.go
@@ -74,6 +74,7 @@ func Cmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -178,6 +179,7 @@ Start:
 					CurrentBranch:            sharedData.initialBranch,
 					Lineage:                  sharedData.config.NormalConfig.Lineage,
 					MainAndPerennialBranches: sharedData.config.MainAndPerennials(),
+					Order:                    sharedData.config.NormalConfig.Order,
 				})
 				if err != nil {
 					fmt.Printf("failed to update proposal stack lineage: %s\n", err.Error())
diff --git a/internal/cmd/ship/shared.go b/internal/cmd/ship/shared.go
index a789dbc70..4346c778e 100644
--- a/internal/cmd/ship/shared.go
+++ b/internal/cmd/ship/shared.go
@@ -161,13 +161,14 @@ func determineSharedShipData(args []string, repo execute.OpenRepoResult, shipStr
 	if !hasTargetBranch {
 		return data, configdomain.ProgramFlowExit, fmt.Errorf(messages.BranchDoesntExist, targetBranchName)
 	}
-	childBranches := validatedConfig.NormalConfig.Lineage.Children(branchToShip)
+	childBranches := validatedConfig.NormalConfig.Lineage.Children(branchToShip, validatedConfig.NormalConfig.Order)
 	proposalsOfChildBranches := LoadProposalsOfChildBranches(LoadProposalsOfChildBranchesArgs{
 		ConnectorOpt:               connector,
 		Lineage:                    validatedConfig.NormalConfig.Lineage,
 		Offline:                    repo.IsOffline,
 		OldBranch:                  branchToShip,
 		OldBranchHasTrackingBranch: branchToShipInfo.HasTrackingBranch(),
+		Order:                      validatedConfig.NormalConfig.Order,
 	})
 	return sharedShipData{
 		branchToShip:             branchToShip,
@@ -204,7 +205,7 @@ func LoadProposalsOfChildBranches(args LoadProposalsOfChildBranchesArgs) []forge
 	if !args.OldBranchHasTrackingBranch {
 		return []forgedomain.Proposal{}
 	}
-	childBranches := args.Lineage.Children(args.OldBranch)
+	childBranches := args.Lineage.Children(args.OldBranch, args.Order)
 	result := make([]forgedomain.Proposal, 0, len(childBranches))
 	for _, childBranch := range childBranches {
 		childProposalOpt, err := proposalFinder.FindProposal(childBranch, args.OldBranch)
@@ -227,6 +228,7 @@ type LoadProposalsOfChildBranchesArgs struct {
 	Offline                    configdomain.Offline
 	OldBranch                  gitdomain.LocalBranchName
 	OldBranchHasTrackingBranch bool
+	Order                      configdomain.Order
 }
 
 func FindProposal(connectorOpt Option[forgedomain.Connector], sourceBranch gitdomain.LocalBranchName, targetBranch Option[gitdomain.LocalBranchName]) Option[forgedomain.Proposal] {
diff --git a/internal/cmd/skip.go b/internal/cmd/skip.go
index 42fc1630b..1595d0f01 100644
--- a/internal/cmd/skip.go
+++ b/internal/cmd/skip.go
@@ -41,6 +41,7 @@ func skipCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/status/reset.go b/internal/cmd/status/reset.go
index 87a4f63de..9065420c6 100644
--- a/internal/cmd/status/reset.go
+++ b/internal/cmd/status/reset.go
@@ -38,6 +38,7 @@ func resetRunstateCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/status/root.go b/internal/cmd/status/root.go
index f4dfab8f9..aa11d4e8a 100644
--- a/internal/cmd/status/root.go
+++ b/internal/cmd/status/root.go
@@ -41,6 +41,7 @@ func RootCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/status/show.go b/internal/cmd/status/show.go
index 6b488c77f..259c40cec 100644
--- a/internal/cmd/status/show.go
+++ b/internal/cmd/status/show.go
@@ -32,6 +32,7 @@ func showRunstateCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/swap/cmd.go b/internal/cmd/swap/cmd.go
index 93df05212..158e0b57e 100644
--- a/internal/cmd/swap/cmd.go
+++ b/internal/cmd/swap/cmd.go
@@ -82,6 +82,7 @@ func Cmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -293,7 +294,7 @@ func determineSwapData(repo execute.OpenRepoResult) (data swapData, flow configd
 	if !hasGrandParentBranch {
 		return data, configdomain.ProgramFlowExit, errors.New(messages.SwapNoGrandParent)
 	}
-	childBranches := validatedConfig.NormalConfig.Lineage.Children(currentBranch)
+	childBranches := validatedConfig.NormalConfig.Lineage.Children(currentBranch, validatedConfig.NormalConfig.Order)
 	children := make([]swapBranch, len(childBranches))
 	for c, childBranch := range childBranches {
 		proposal := None[forgedomain.Proposal]()
@@ -372,7 +373,7 @@ func determineSwapData(repo execute.OpenRepoResult) (data swapData, flow configd
 
 func swapProgram(repo execute.OpenRepoResult, data swapData, finalMessages stringslice.Collector) program.Program {
 	prog := NewMutable(&program.Program{})
-	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Frontend)
+	data.config.CleanupLineage(data.branchesSnapshot.Branches, data.nonExistingBranches, finalMessages, repo.Frontend, data.config.NormalConfig.Order)
 	swapGitOperationsProgram(swapGitOperationsProgramArgs{
 		children: data.children,
 		current: swapBranch{
@@ -405,6 +406,7 @@ func swapProgram(repo execute.OpenRepoResult, data swapData, finalMessages strin
 				CurrentBranch:            data.currentBranchName,
 				Lineage:                  data.config.NormalConfig.Lineage,
 				MainAndPerennialBranches: data.config.MainAndPerennials(),
+				Order:                    data.config.NormalConfig.Order,
 			})
 		}
 	}
diff --git a/internal/cmd/switch.go b/internal/cmd/switch.go
index 616a63edd..8c5e0f3a1 100644
--- a/internal/cmd/switch.go
+++ b/internal/cmd/switch.go
@@ -29,6 +29,7 @@ func switchCmd() *cobra.Command {
 	addAllFlag, readAllFlag := flags.All("list both remote-tracking and local branches")
 	addDisplayTypesFlag, readDisplayTypesFlag := flags.Displaytypes()
 	addMergeFlag, readMergeFlag := flags.Merge()
+	addOrderFlag, readOrderFlag := flags.Order()
 	addTypeFlag, readTypeFlag := flags.BranchType()
 	addVerboseFlag, readVerboseFlag := flags.Verbose()
 	cmd := cobra.Command{
@@ -42,8 +43,9 @@ func switchCmd() *cobra.Command {
 			allBranches, errAllBranches := readAllFlag(cmd)
 			displayTypes, errDisplayTypes := readDisplayTypesFlag(cmd)
 			merge, errMerge := readMergeFlag(cmd)
+			order, errOrder := readOrderFlag(cmd)
 			verbose, errVerbose := readVerboseFlag(cmd)
-			if err := cmp.Or(errBranchTypes, errAllBranches, errDisplayTypes, errMerge, errVerbose); err != nil {
+			if err := cmp.Or(errBranchTypes, errAllBranches, errDisplayTypes, errMerge, errOrder, errVerbose); err != nil {
 				return err
 			}
 			cliConfig := cliconfig.New(cliconfig.NewArgs{
@@ -51,6 +53,7 @@ func switchCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        order,
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -68,6 +71,7 @@ func switchCmd() *cobra.Command {
 	addAllFlag(&cmd)
 	addDisplayTypesFlag(&cmd)
 	addMergeFlag(&cmd)
+	addOrderFlag(&cmd)
 	addTypeFlag(&cmd)
 	addVerboseFlag(&cmd)
 	return &cmd
@@ -114,6 +118,7 @@ Start:
 		ExcludeBranches:   gitdomain.LocalBranchNames{},
 		Lineage:           data.config.NormalConfig.Lineage,
 		MainBranch:        repo.UnvalidatedConfig.UnvalidatedConfig.MainBranch,
+		Order:             data.config.NormalConfig.Order,
 		Regexes:           data.regexes,
 		ShowAllBranches:   false,
 		UnknownBranchType: unknownBranchType,
diff --git a/internal/cmd/sync/cmd.go b/internal/cmd/sync/cmd.go
index 8d8497359..dd8c3ceef 100644
--- a/internal/cmd/sync/cmd.go
+++ b/internal/cmd/sync/cmd.go
@@ -82,6 +82,7 @@ func Cmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     detached,
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: pushBranches,
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -141,7 +142,7 @@ Start:
 	if err = validateSyncData(data); err != nil {
 		return err
 	}
-	data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, repo.FinalMessages, repo.Backend)
+	data.config.CleanupLineage(data.branchInfos, data.nonExistingBranches, repo.FinalMessages, repo.Backend, data.config.NormalConfig.Order)
 	runProgram := NewMutable(&program.Program{})
 	branchesToDelete := set.New[gitdomain.LocalBranchName]()
 	BranchesProgram(data.branchesToSync, BranchProgramArgs{
@@ -183,6 +184,7 @@ Start:
 							CurrentBranch:            data.initialBranch,
 							Lineage:                  data.config.NormalConfig.Lineage,
 							MainAndPerennialBranches: data.config.MainAndPerennials(),
+							Order:                    data.config.NormalConfig.Order,
 						},
 					},
 				)
@@ -370,7 +372,7 @@ func determineSyncData(repo execute.OpenRepoResult, args determineSyncDataArgs)
 	case args.syncAllBranches.Enabled():
 		branchNamesToSync = localBranches
 	case args.syncStack.Enabled():
-		branchNamesToSync = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialAndMain)
+		branchNamesToSync = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialAndMain, validatedConfig.NormalConfig.Order)
 	default:
 		branchNamesToSync = gitdomain.LocalBranchNames{initialBranch}
 	}
@@ -402,7 +404,7 @@ func determineSyncData(repo execute.OpenRepoResult, args determineSyncDataArgs)
 	default:
 		shouldPushTags = validatedConfig.IsMainOrPerennialBranch(initialBranch)
 	}
-	allBranchNamesToSync := validatedConfig.NormalConfig.Lineage.BranchesAndAncestors(branchNamesToSync)
+	allBranchNamesToSync := validatedConfig.NormalConfig.Lineage.BranchesAndAncestors(branchNamesToSync, validatedConfig.NormalConfig.Order)
 	if repo.UnvalidatedConfig.NormalConfig.Detached {
 		allBranchNamesToSync = allBranchNamesToSync.Remove(perennialAndMain...)
 	}
diff --git a/internal/cmd/sync/remove_branch.go b/internal/cmd/sync/remove_branch.go
index 80b2d19ad..b82144b0c 100644
--- a/internal/cmd/sync/remove_branch.go
+++ b/internal/cmd/sync/remove_branch.go
@@ -9,7 +9,7 @@ import (
 )
 
 func RemoveBranchConfiguration(args RemoveBranchConfigurationArgs) {
-	childBranches := args.Lineage.Children(args.Branch)
+	childBranches := args.Lineage.Children(args.Branch, args.Order)
 	for _, child := range childBranches {
 		args.Program.Value.Add(&opcodes.LineageParentSetToGrandParent{Branch: child})
 	}
@@ -19,5 +19,6 @@ func RemoveBranchConfiguration(args RemoveBranchConfigurationArgs) {
 type RemoveBranchConfigurationArgs struct {
 	Branch  gitdomain.LocalBranchName
 	Lineage configdomain.Lineage
+	Order   configdomain.Order
 	Program Mutable[program.Program]
 }
diff --git a/internal/cmd/sync/sync_deleted_branch.go b/internal/cmd/sync/sync_deleted_branch.go
index 40522152b..2d185ea1a 100644
--- a/internal/cmd/sync/sync_deleted_branch.go
+++ b/internal/cmd/sync/sync_deleted_branch.go
@@ -80,6 +80,7 @@ func syncDeleteLocalBranchProgram(branch gitdomain.LocalBranchName, args BranchP
 	RemoveBranchConfiguration(RemoveBranchConfigurationArgs{
 		Branch:  branch,
 		Lineage: args.Config.NormalConfig.Lineage,
+		Order:   args.Config.NormalConfig.Order,
 		Program: args.Program,
 	})
 }
diff --git a/internal/cmd/undo.go b/internal/cmd/undo.go
index becfcedb9..90f911868 100644
--- a/internal/cmd/undo.go
+++ b/internal/cmd/undo.go
@@ -43,6 +43,7 @@ func undoCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
diff --git a/internal/cmd/up.go b/internal/cmd/up.go
index bfa2d5c46..916bbab79 100644
--- a/internal/cmd/up.go
+++ b/internal/cmd/up.go
@@ -45,6 +45,7 @@ func upCmd() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     None[configdomain.Detached](),
 				DryRun:       None[configdomain.DryRun](),
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -89,7 +90,7 @@ Start:
 	}
 
 	// Get the child branches from lineage
-	children := repo.UnvalidatedConfig.NormalConfig.Lineage.Children(currentBranch)
+	children := repo.UnvalidatedConfig.NormalConfig.Lineage.Children(currentBranch, repo.UnvalidatedConfig.NormalConfig.Order)
 	var child gitdomain.LocalBranchName
 	switch len(children) {
 	case 0:
@@ -134,6 +135,7 @@ Start:
 		ExcludeBranches:   gitdomain.LocalBranchNames{},
 		Lineage:           repo.UnvalidatedConfig.NormalConfig.Lineage,
 		MainBranch:        repo.UnvalidatedConfig.UnvalidatedConfig.MainBranch,
+		Order:             repo.UnvalidatedConfig.NormalConfig.Order,
 		Regexes:           []*regexp.Regexp{},
 		ShowAllBranches:   false,
 		UnknownBranchType: repo.UnvalidatedConfig.NormalConfig.UnknownBranchType,
diff --git a/internal/cmd/walk.go b/internal/cmd/walk.go
index 7dae16c46..1e7cf1e37 100644
--- a/internal/cmd/walk.go
+++ b/internal/cmd/walk.go
@@ -91,6 +91,7 @@ func walkCommand() *cobra.Command {
 				AutoSync:     None[configdomain.AutoSync](),
 				Detached:     Some(configdomain.Detached(true)),
 				DryRun:       dryRun,
+				Order:        None[configdomain.Order](),
 				PushBranches: None[configdomain.PushBranches](),
 				Stash:        None[configdomain.Stash](),
 				Verbose:      verbose,
@@ -282,7 +283,7 @@ func determineWalkData(repo execute.OpenRepoResult, all configdomain.AllBranches
 	case all.Enabled():
 		branchesToWalk = localBranches.Remove(perennialBranchNames...)
 	case stack.Enabled():
-		branchesToWalk = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialBranchNames)
+		branchesToWalk = validatedConfig.NormalConfig.Lineage.BranchLineageWithoutRoot(initialBranch, perennialBranchNames, validatedConfig.NormalConfig.Order)
 	}
 	return walkData{
 		branchInfosLastRun: branchInfosLastRun,
diff --git a/internal/config/cliconfig/cli_config.go b/internal/config/cliconfig/cli_config.go
index 81a288dd7..e678120d2 100644
--- a/internal/config/cliconfig/cli_config.go
+++ b/internal/config/cliconfig/cli_config.go
@@ -12,6 +12,7 @@ type NewArgs struct {
 	AutoSync     Option[configdomain.AutoSync]
 	Detached     Option[configdomain.Detached]
 	DryRun       Option[configdomain.DryRun]
+	Order        Option[configdomain.Order]
 	PushBranches Option[configdomain.PushBranches]
 	Stash        Option[configdomain.Stash]
 	Verbose      Option[configdomain.Verbose]
@@ -45,6 +46,7 @@ func New(args NewArgs) configdomain.PartialConfig {
 		NewBranchType:            None[configdomain.NewBranchType](),
 		ObservedRegex:            None[configdomain.ObservedRegex](),
 		Offline:                  None[configdomain.Offline](),
+		Order:                    args.Order,
 		PerennialBranches:        gitdomain.LocalBranchNames{},
 		PerennialRegex:           None[configdomain.PerennialRegex](),
 		ProposalsShowLineage:     None[forgedomain.ProposalsShowLineage](),
diff --git a/internal/config/configdomain/key.go b/internal/config/configdomain/key.go
index 17c86c0bf..cd233e1a7 100644
--- a/internal/config/configdomain/key.go
+++ b/internal/config/configdomain/key.go
@@ -96,6 +96,7 @@ const (
 	KeyObservedRegex                       = Key("git-town.observed-regex")
 	KeyObsoleteSyncBeforeShip              = Key("git-town.sync-before-ship")
 	KeyOffline                             = Key("git-town.offline")
+	KeyOrder                               = Key("git-town.order")
 	KeyPerennialBranches                   = Key("git-town.perennial-branches")
 	KeyPerennialRegex                      = Key("git-town.perennial-regex")
 	KeyProposalsShowLineage                = Key("git-town.proposals-show-lineage")
@@ -160,6 +161,7 @@ var keys = []Key{
 	KeyObservedRegex,
 	KeyObsoleteSyncBeforeShip,
 	KeyOffline,
+	KeyOrder,
 	KeyPerennialBranches,
 	KeyPerennialRegex,
 	KeyProposalsShowLineage,
diff --git a/internal/config/configdomain/lineage.go b/internal/config/configdomain/lineage.go
index 13752df7f..2a3161771 100644
--- a/internal/config/configdomain/lineage.go
+++ b/internal/config/configdomain/lineage.go
@@ -69,21 +69,21 @@ func (self Lineage) BranchAndAncestorsWithoutRoot(branchName gitdomain.LocalBran
 
 // BranchLineage method returns a complete lineage chain for a given branch, including the branch itself,
 // all its ancestors, and all its descendants.
-func (self Lineage) BranchLineage(branch gitdomain.LocalBranchName) gitdomain.LocalBranchNames {
-	return append(append(self.Ancestors(branch), branch), self.Descendants(branch)...)
+func (self Lineage) BranchLineage(branch gitdomain.LocalBranchName, order Order) gitdomain.LocalBranchNames {
+	return append(append(self.Ancestors(branch), branch), self.Descendants(branch, order)...)
 }
 
 // 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 {
+func (self Lineage) BranchLineageWithoutRoot(branch gitdomain.LocalBranchName, perennialBranches gitdomain.LocalBranchNames, order Order) gitdomain.LocalBranchNames {
 	if self.Parent(branch).IsNone() {
 		result := gitdomain.LocalBranchNames{}
 		if !perennialBranches.Contains(branch) {
 			result = append(result, branch)
 		}
-		return append(result, self.Descendants(branch)...)
+		return append(result, self.Descendants(branch, order)...)
 	}
-	return append(append(self.AncestorsWithoutRoot(branch), branch), self.Descendants(branch)...)
+	return append(append(self.AncestorsWithoutRoot(branch), branch), self.Descendants(branch, order)...)
 }
 
 // BranchNames provides the names of all branches in this Lineage, sorted alphabetically.
@@ -95,12 +95,12 @@ func (self Lineage) BranchNames() gitdomain.LocalBranchNames {
 
 // BranchesAndAncestors provides the full lineage for the branches with the given names,
 // including the branches themselves.
-func (self Lineage) BranchesAndAncestors(branchNames gitdomain.LocalBranchNames) gitdomain.LocalBranchNames {
+func (self Lineage) BranchesAndAncestors(branchNames gitdomain.LocalBranchNames, order Order) gitdomain.LocalBranchNames {
 	result := branchNames
 	for _, branchName := range branchNames {
 		result = slice.AppendAllMissing(result, self.Ancestors(branchName)...)
 	}
-	return self.OrderHierarchically(result)
+	return self.OrderHierarchically(result, order)
 }
 
 // provides all branches for which the parent is known
@@ -111,23 +111,28 @@ func (self Lineage) BranchesWithParents() gitdomain.LocalBranchNames {
 }
 
 // Children provides the names of all branches that have the given branch as their parent.
-func (self Lineage) Children(branch gitdomain.LocalBranchName) gitdomain.LocalBranchNames {
+func (self Lineage) Children(branch gitdomain.LocalBranchName, order Order) gitdomain.LocalBranchNames {
 	result := gitdomain.LocalBranchNames{}
 	for child, parent := range mapstools.SortedKeyValues(self.data) {
 		if parent == branch {
 			result = append(result, child)
 		}
 	}
-	slice.NaturalSort(result)
+	switch order {
+	case OrderAsc:
+		slice.NaturalSort(result)
+	case OrderDesc:
+		slice.NaturalSortReverse(result)
+	}
 	return result
 }
 
 // Descendants provides all branches that depend on the given branch in its lineage.
-func (self Lineage) Descendants(branch gitdomain.LocalBranchName) gitdomain.LocalBranchNames {
+func (self Lineage) Descendants(branch gitdomain.LocalBranchName, order Order) gitdomain.LocalBranchNames {
 	result := gitdomain.LocalBranchNames{}
-	for _, child := range self.Children(branch) {
+	for _, child := range self.Children(branch, order) {
 		result = append(result, child)
-		result = append(result, self.Descendants(child)...)
+		result = append(result, self.Descendants(child, order)...)
 	}
 	return result
 }
@@ -187,10 +192,10 @@ func (self Lineage) Merge(other Lineage) Lineage {
 }
 
 // OrderHierarchically provides the given branches sorted so that ancestor branches come before their descendants.
-func (self Lineage) OrderHierarchically(branches gitdomain.LocalBranchNames) gitdomain.LocalBranchNames {
+func (self Lineage) OrderHierarchically(branches gitdomain.LocalBranchNames, order Order) gitdomain.LocalBranchNames {
 	result := make(gitdomain.LocalBranchNames, 0, len(self.data))
 	for _, root := range self.Roots() {
-		self.addChildrenHierarchically(&result, root, branches)
+		self.addChildrenHierarchically(&result, root, branches, order)
 	}
 	return result.AppendAllMissing(branches)
 }
@@ -256,12 +261,12 @@ func (self Lineage) YoungestAncestorWithin(branch gitdomain.LocalBranchName, whi
 	}
 }
 
-func (self Lineage) addChildrenHierarchically(result *gitdomain.LocalBranchNames, currentBranch gitdomain.LocalBranchName, allBranches gitdomain.LocalBranchNames) {
+func (self Lineage) addChildrenHierarchically(result *gitdomain.LocalBranchNames, currentBranch gitdomain.LocalBranchName, allBranches gitdomain.LocalBranchNames, order Order) {
 	if allBranches.Contains(currentBranch) {
 		*result = append(*result, currentBranch)
 	}
-	for _, child := range self.Children(currentBranch) {
-		self.addChildrenHierarchically(result, child, allBranches)
+	for _, child := range self.Children(currentBranch, order) {
+		self.addChildrenHierarchically(result, child, allBranches, order)
 	}
 }
 
diff --git a/internal/config/configdomain/order.go b/internal/config/configdomain/order.go
new file mode 100644
index 000000000..43e1c9b05
--- /dev/null
+++ b/internal/config/configdomain/order.go
@@ -0,0 +1,38 @@
+package configdomain
+
+import (
+	"fmt"
+	"strings"
+
+	. "github.com/git-town/git-town/v22/pkg/prelude"
+)
+
+// Order describes the sort order for displaying branch lists.
+type Order string
+
+const (
+	OrderAsc  Order = "asc"
+	OrderDesc Order = "desc"
+)
+
+var OrderValues = []Order{
+	OrderAsc,
+	OrderDesc,
+}
+
+func (self Order) String() string {
+	return string(self)
+}
+
+func ParseOrder(value string, source Key) (Option[Order], error) {
+	switch strings.ToLower(value) {
+	case "":
+		return None[Order](), nil
+	case "a", "as", "asc":
+		return Some(OrderAsc), nil
+	case "d", "de", "des", "desc":
+		return Some(OrderDesc), nil
+	default:
+		return None[Order](), fmt.Errorf("invalid value for %s: %q", source, value)
+	}
+}
diff --git a/internal/config/configdomain/partial_config.go b/internal/config/configdomain/partial_config.go
index 527e99503..e550811e2 100644
--- a/internal/config/configdomain/partial_config.go
+++ b/internal/config/configdomain/partial_config.go
@@ -42,6 +42,7 @@ type PartialConfig struct {
 	NewBranchType            Option[NewBranchType]
 	ObservedRegex            Option[ObservedRegex]
 	Offline                  Option[Offline]
+	Order                    Option[Order]
 	PerennialBranches        gitdomain.LocalBranchNames
 	PerennialRegex           Option[PerennialRegex]
 	ProposalsShowLineage     Option[forgedomain.ProposalsShowLineage]
@@ -96,6 +97,7 @@ func (self PartialConfig) Merge(other PartialConfig) PartialConfig {
 		NewBranchType:            other.NewBranchType.Or(self.NewBranchType),
 		ObservedRegex:            other.ObservedRegex.Or(self.ObservedRegex),
 		Offline:                  other.Offline.Or(self.Offline),
+		Order:                    other.Order.Or(self.Order),
 		PerennialBranches:        append(other.PerennialBranches, self.PerennialBranches...),
 		PerennialRegex:           other.PerennialRegex.Or(self.PerennialRegex),
 		ProposalsShowLineage:     other.ProposalsShowLineage.Or(self.ProposalsShowLineage),
diff --git a/internal/config/configfile/data.go b/internal/config/configfile/data.go
index bb4b7c04a..0cab8be4d 100644
--- a/internal/config/configfile/data.go
+++ b/internal/config/configfile/data.go
@@ -24,6 +24,7 @@ type Branches struct {
 	FeatureRegex      *string  `toml:"feature-regex"`
 	Main              *string  `toml:"main"`
 	ObservedRegex     *string  `toml:"observed-regex"`
+	Order             *string  `toml:"order"`
 	PerennialRegex    *string  `toml:"perennial-regex"`
 	Perennials        []string `toml:"perennials"`
 	UnknownType       *string  `toml:"unknown-type"`
diff --git a/internal/config/configfile/load.go b/internal/config/configfile/load.go
index 40c09c79d..4883cc409 100644
--- a/internal/config/configfile/load.go
+++ b/internal/config/configfile/load.go
@@ -58,6 +58,7 @@ func Validate(data Data, finalMessages stringslice.Collector) (configdomain.Part
 	var mainBranch Option[gitdomain.LocalBranchName]
 	var newBranchType Option[configdomain.NewBranchType]
 	var observedRegex Option[configdomain.ObservedRegex]
+	var order Option[configdomain.Order]
 	var perennialBranches gitdomain.LocalBranchNames
 	var perennialRegex Option[configdomain.PerennialRegex]
 	var proposalsShowLineage Option[forgedomain.ProposalsShowLineage]
@@ -136,6 +137,10 @@ func Validate(data Data, finalMessages stringslice.Collector) (configdomain.Part
 				observedRegex = Some(configdomain.ObservedRegex{VerifiedRegex: verifiedRegex})
 			}
 		}
+		if data.Branches.Order != nil {
+			order, err = configdomain.ParseOrder(*data.Branches.Order, "config file")
+			ec.Check(err)
+		}
 		if data.Branches.UnknownType != nil {
 			branchType, err := configdomain.ParseBranchType(*data.Branches.UnknownType)
 			ec.Check(err)
@@ -275,6 +280,7 @@ func Validate(data Data, finalMessages stringslice.Collector) (configdomain.Part
 		AutoResolve:              autoResolve,
 		ObservedRegex:            observedRegex,
 		Offline:                  None[configdomain.Offline](),
+		Order:                    order,
 		PerennialBranches:        perennialBranches,
 		PerennialRegex:           perennialRegex,
 		ProposalsShowLineage:     proposalsShowLineage,
diff --git a/internal/config/configfile/save.go b/internal/config/configfile/save.go
index aeb5c639a..ec99dc0db 100644
--- a/internal/config/configfile/save.go
+++ b/internal/config/configfile/save.go
@@ -22,12 +22,16 @@ func RenderTOML(data configdomain.PartialConfig) string {
 
 	main, hasMain := data.MainBranch.Get()
 	hasPerennialBranches := len(data.PerennialBranches) > 0
+	order, hasOrder := data.Order.Get()
 	perennialRegex, hasPerennialRegex := data.PerennialRegex.Get()
 	if hasMain || hasPerennialBranches || hasPerennialRegex {
 		result.WriteString("\n[branches]\n")
 		if hasMain {
 			result.WriteString(fmt.Sprintf("main = %q\n", main))
 		}
+		if hasOrder {
+			result.WriteString(fmt.Sprintf("order = %q\n", order))
+		}
 		if hasPerennialBranches {
 			result.WriteString(fmt.Sprintf("perennials = %s\n", RenderPerennialBranches(data.PerennialBranches)))
 		}
diff --git a/internal/config/envconfig/load.go b/internal/config/envconfig/load.go
index 885809f81..f77708142 100644
--- a/internal/config/envconfig/load.go
+++ b/internal/config/envconfig/load.go
@@ -30,6 +30,7 @@ const (
 	mainBranch               = "GIT_TOWN_MAIN_BRANCH"
 	newBranchType            = "GIT_TOWN_NEW_BRANCH_TYPE"
 	observedRegex            = "GIT_TOWN_OBSERVED_REGEX"
+	order                    = "GIT_TOWN_ORDER"
 	originHostname           = "GIT_TOWN_ORIGIN_HOSTNAME"
 	offline                  = "GIT_TOWN_OFFLINE"
 	perennialBranches        = "GIT_TOWN_PERENNIAL_BRANCHES"
@@ -62,6 +63,7 @@ func Load(env EnvVars) (configdomain.PartialConfig, error) {
 	gitlabConnectorType, errGitLabConnectorType := forgedomain.ParseGitLabConnectorType(env.Get(gitlabConnectorType))
 	newBranchType, errNewBranchType := configdomain.ParseBranchType(env.Get(newBranchType))
 	observedRegex, errObservedRegex := configdomain.ParseObservedRegex(env.Get(observedRegex))
+	order, errOrder := configdomain.ParseOrder(env.Get(order), order)
 	offline, errOffline := gohacks.ParseBoolOpt[configdomain.Offline](env.Get(offline), offline)
 	perennialRegex, errPerennialRegex := configdomain.ParsePerennialRegex(env.Get(perennialRegex))
 	proposalsShowLineage, errProposalsShowLineage := forgedomain.ParseProposalsShowLineage(env.Get(proposalsShowLineage))
@@ -91,6 +93,7 @@ func Load(env EnvVars) (configdomain.PartialConfig, error) {
 		errNewBranchType,
 		errObservedRegex,
 		errOffline,
+		errOrder,
 		errPerennialRegex,
 		errProposalsShowLineage,
 		errPushBranches,
@@ -134,6 +137,7 @@ func Load(env EnvVars) (configdomain.PartialConfig, error) {
 		NewBranchType:            configdomain.NewBranchTypeOpt(newBranchType),
 		ObservedRegex:            observedRegex,
 		Offline:                  offline,
+		Order:                    order,
 		PerennialBranches:        gitdomain.ParseLocalBranchNames(env.Get(perennialBranches)),
 		PerennialRegex:           perennialRegex,
 		ProposalsShowLineage:     proposalsShowLineage,
diff --git a/internal/config/gitconfig/high_level.go b/internal/config/gitconfig/high_level.go
index f8ee0c980..25597ad9e 100644
--- a/internal/config/gitconfig/high_level.go
+++ b/internal/config/gitconfig/high_level.go
@@ -110,6 +110,10 @@ func RemoveObservedRegex(runner subshelldomain.Runner) error {
 	return RemoveConfigValue(runner, configdomain.ConfigScopeLocal, configdomain.KeyObservedRegex)
 }
 
+func RemoveOrder(runner subshelldomain.Runner) error {
+	return RemoveConfigValue(runner, configdomain.ConfigScopeLocal, configdomain.KeyOrder)
+}
+
 func RemoveOriginHostname(runner subshelldomain.Runner) error {
 	return RemoveConfigValue(runner, configdomain.ConfigScopeLocal, configdomain.KeyHostingOriginHostname)
 }
@@ -259,6 +263,10 @@ func SetOffline(runner subshelldomain.Runner, value configdomain.Offline) error
 	return SetConfigValue(runner, configdomain.ConfigScopeGlobal, configdomain.KeyOffline, value.String())
 }
 
+func SetOrder(runner subshelldomain.Runner, value configdomain.Order, scope configdomain.ConfigScope) error {
+	return SetConfigValue(runner, scope, configdomain.KeyOrder, value.String())
+}
+
 func SetOriginHostname(runner subshelldomain.Runner, hostname configdomain.HostingOriginHostname, scope configdomain.ConfigScope) error {
 	return SetConfigValue(runner, scope, configdomain.KeyHostingOriginHostname, hostname.String())
 }
diff --git a/internal/config/normal_config.go b/internal/config/normal_config.go
index edb6762ab..095beffba 100644
--- a/internal/config/normal_config.go
+++ b/internal/config/normal_config.go
@@ -52,6 +52,7 @@ type NormalConfig struct {
 	NewBranchType            Option[configdomain.NewBranchType]
 	ObservedRegex            Option[configdomain.ObservedRegex]
 	Offline                  configdomain.Offline
+	Order                    configdomain.Order
 	PerennialBranches        gitdomain.LocalBranchNames
 	PerennialRegex           Option[configdomain.PerennialRegex]
 	ProposalsShowLineage     forgedomain.ProposalsShowLineage
@@ -104,6 +105,7 @@ func (self *NormalConfig) OverwriteWith(other configdomain.PartialConfig) Normal
 		NewBranchType:            other.NewBranchType.Or(self.NewBranchType),
 		ObservedRegex:            other.ObservedRegex.Or(self.ObservedRegex),
 		Offline:                  other.Offline.GetOr(self.Offline),
+		Order:                    other.Order.GetOr(self.Order),
 		PerennialBranches:        other.PerennialBranches.AppendAllMissing(self.PerennialBranches),
 		PerennialRegex:           other.PerennialRegex.Or(self.PerennialRegex),
 		ProposalsShowLineage:     other.ProposalsShowLineage.GetOr(self.ProposalsShowLineage),
@@ -249,6 +251,7 @@ func DefaultNormalConfig() NormalConfig {
 		NewBranchType:            None[configdomain.NewBranchType](),
 		ObservedRegex:            None[configdomain.ObservedRegex](),
 		Offline:                  false,
+		Order:                    configdomain.OrderAsc,
 		PerennialBranches:        gitdomain.LocalBranchNames{},
 		PerennialRegex:           None[configdomain.PerennialRegex](),
 		ProposalsShowLineage:     forgedomain.ProposalsShowLineageNone,
@@ -294,6 +297,7 @@ func NewNormalConfigFromPartial(partial configdomain.PartialConfig, defaults Nor
 		NewBranchType:            partial.NewBranchType.Or(defaults.NewBranchType),
 		ObservedRegex:            partial.ObservedRegex,
 		Offline:                  partial.Offline.GetOr(defaults.Offline),
+		Order:                    partial.Order.GetOr(defaults.Order),
 		PerennialBranches:        partial.PerennialBranches,
 		PerennialRegex:           partial.PerennialRegex,
 		ProposalsShowLineage:     partial.ProposalsShowLineage.GetOr(defaults.ProposalsShowLineage),
diff --git a/internal/config/parse_snapshot.go b/internal/config/parse_snapshot.go
index 9144c857b..952bf5698 100644
--- a/internal/config/parse_snapshot.go
+++ b/internal/config/parse_snapshot.go
@@ -91,6 +91,7 @@ func NewPartialConfigFromSnapshot(snapshot configdomain.SingleSnapshot, updateOu
 	newBranchTypeValue, errNewBranchType := configdomain.ParseBranchType(snapshot[configdomain.KeyNewBranchType])
 	newBranchType := configdomain.NewBranchTypeOpt(newBranchTypeValue)
 	observedRegex, errObservedRegex := configdomain.ParseObservedRegex(snapshot[configdomain.KeyObservedRegex])
+	order, errOrder := configdomain.ParseOrder(snapshot[configdomain.KeyOrder], configdomain.KeyOrder)
 	offline, errOffline := gohacks.ParseBoolOpt[configdomain.Offline](snapshot[configdomain.KeyOffline], configdomain.KeyOffline.String())
 	perennialRegex, errPerennialRegex := configdomain.ParsePerennialRegex(snapshot[configdomain.KeyPerennialRegex])
 	proposalsShowLineage, errProposalsShowLineage := forgedomain.ParseProposalsShowLineage(snapshot[configdomain.KeyProposalsShowLineage])
@@ -120,6 +121,7 @@ func NewPartialConfigFromSnapshot(snapshot configdomain.SingleSnapshot, updateOu
 		errLineage,
 		errNewBranchType,
 		errObservedRegex,
+		errOrder,
 		errOffline,
 		errPerennialRegex,
 		errProposalsShowLineage,
@@ -162,6 +164,7 @@ func NewPartialConfigFromSnapshot(snapshot configdomain.SingleSnapshot, updateOu
 		MainBranch:               gitdomain.NewLocalBranchNameOption(snapshot[configdomain.KeyMainBranch]),
 		NewBranchType:            newBranchType,
 		ObservedRegex:            observedRegex,
+		Order:                    order,
 		Offline:                  offline,
 		PerennialBranches:        gitdomain.ParseLocalBranchNames(snapshot[configdomain.KeyPerennialBranches]),
 		PerennialRegex:           perennialRegex,
diff --git a/internal/config/validated_config.go b/internal/config/validated_config.go
index ffb85c732..c5c66bf53 100644
--- a/internal/config/validated_config.go
+++ b/internal/config/validated_config.go
@@ -45,9 +45,9 @@ func (self *ValidatedConfig) BranchesOfType(branches gitdomain.LocalBranchNames,
 }
 
 // removes the given branch from the lineage, and updates its children
-func (self *ValidatedConfig) CleanupBranchFromLineage(runner subshelldomain.Runner, branch gitdomain.LocalBranchName) {
+func (self *ValidatedConfig) CleanupBranchFromLineage(runner subshelldomain.Runner, branch gitdomain.LocalBranchName, order configdomain.Order) {
 	parent, hasParent := self.NormalConfig.Lineage.Parent(branch).Get()
-	children := self.NormalConfig.Lineage.Children(branch)
+	children := self.NormalConfig.Lineage.Children(branch, order)
 	for _, child := range children {
 		if hasParent {
 			self.NormalConfig.Lineage = self.NormalConfig.Lineage.Set(child, parent)
@@ -61,8 +61,8 @@ func (self *ValidatedConfig) CleanupBranchFromLineage(runner subshelldomain.Runn
 	_ = gitconfig.RemoveParent(runner, branch)
 }
 
-func (self *ValidatedConfig) CleanupLineage(branchInfos gitdomain.BranchInfos, nonExistingBranches gitdomain.LocalBranchNames, finalMessages stringslice.Collector, runner subshelldomain.Runner) {
-	self.RemoveDeletedBranchesFromLineage(branchInfos, nonExistingBranches, runner)
+func (self *ValidatedConfig) CleanupLineage(branchInfos gitdomain.BranchInfos, nonExistingBranches gitdomain.LocalBranchNames, finalMessages stringslice.Collector, runner subshelldomain.Runner, order configdomain.Order) {
+	self.RemoveDeletedBranchesFromLineage(branchInfos, nonExistingBranches, runner, order)
 	self.NormalConfig.RemovePerennialAncestors(runner, finalMessages)
 }
 
@@ -77,9 +77,9 @@ func (self *ValidatedConfig) MainAndPerennials() gitdomain.LocalBranchNames {
 	return append(gitdomain.LocalBranchNames{self.ValidatedConfigData.MainBranch}, self.NormalConfig.PerennialBranches...)
 }
 
-func (self *ValidatedConfig) RemoveDeletedBranchesFromLineage(branchInfos gitdomain.BranchInfos, nonExistingBranches gitdomain.LocalBranchNames, runner subshelldomain.Runner) {
+func (self *ValidatedConfig) RemoveDeletedBranchesFromLineage(branchInfos gitdomain.BranchInfos, nonExistingBranches gitdomain.LocalBranchNames, runner subshelldomain.Runner, order configdomain.Order) {
 	for _, nonExistingBranch := range nonExistingBranches {
-		self.CleanupBranchFromLineage(runner, nonExistingBranch)
+		self.CleanupBranchFromLineage(runner, nonExistingBranch, order)
 	}
 	for _, entry := range self.NormalConfig.Lineage.Entries() {
 		childDoesntExist := nonExistingBranches.Contains(entry.Child)
diff --git a/internal/forge/proposal_stack_lineage_builder.go b/internal/forge/proposal_stack_lineage_builder.go
index 072b9d198..013b7cf2b 100644
--- a/internal/forge/proposal_stack_lineage_builder.go
+++ b/internal/forge/proposal_stack_lineage_builder.go
@@ -17,6 +17,7 @@ type ProposalStackLineageArgs struct {
 	CurrentBranch            gitdomain.LocalBranchName
 	Lineage                  configdomain.Lineage
 	MainAndPerennialBranches gitdomain.LocalBranchNames
+	Order                    configdomain.Order
 }
 
 func NewProposalStackLineageBuilder(args ProposalStackLineageArgs, lineageTree OptionalMutable[ProposalStackLineageTree]) Option[ProposalStackLineageBuilder] {
diff --git a/internal/forge/proposal_stack_lineage_tree.go b/internal/forge/proposal_stack_lineage_tree.go
index a56fbc104..a1f5ad789 100644
--- a/internal/forge/proposal_stack_lineage_tree.go
+++ b/internal/forge/proposal_stack_lineage_tree.go
@@ -76,7 +76,7 @@ func addDescendantNodes(branch gitdomain.LocalBranchName, args ProposalStackLine
 	}
 	visited[branch] = branchNode
 
-	children := args.Lineage.Children(branch)
+	children := args.Lineage.Children(branch, args.Order)
 	for _, child := range children {
 		if err := addDescendantNodes(child, args, visited, tree); err != nil {
 			return err
@@ -160,7 +160,7 @@ func findRelevantChildren(
 	args ProposalStackLineageArgs,
 	ancestors gitdomain.LocalBranchNames,
 ) ([]childWithProposal, error) {
-	children := args.Lineage.Children(ancestor)
+	children := args.Lineage.Children(ancestor, args.Order)
 	var relevantChildren []childWithProposal
 
 	for _, child := range children {
diff --git a/internal/gohacks/slice/natural_sort_reverse.go b/internal/gohacks/slice/natural_sort_reverse.go
new file mode 100644
index 000000000..8ae4f81c9
--- /dev/null
+++ b/internal/gohacks/slice/natural_sort_reverse.go
@@ -0,0 +1,15 @@
+package slice
+
+import (
+	"fmt"
+	"slices"
+)
+
+// sorts the given elements in reverse natural sort order
+func NaturalSortReverse[T fmt.Stringer](list []T) {
+	if len(list) < 2 {
+		return
+	}
+	NaturalSort(list)
+	slices.Reverse(list)
+}
diff --git a/internal/messages/en.go b/internal/messages/en.go
index 29a72670a..7b915eac3 100644
--- a/internal/messages/en.go
+++ b/internal/messages/en.go
@@ -210,6 +210,7 @@ Please upgrade to the new format: create.new-branch-type = "prototype"`
 	OfflineNotAllowed           = "this command requires an active internet connection"
 	OpcodeUnknown               = "unknown opcode: %q, run \"git town status reset\" to reset it"
 	OpenChangesProblem          = "cannot determine open changes: %w"
+	Order                       = "Order: %s\n"
 	OriginHostnamePrompt        = "Origin hostname override: "
 	OriginHostnameResult        = "Origin hostname: %s\n"
 
diff --git a/internal/setup/enter.go b/internal/setup/enter.go
index be752d1d7..45ca13001 100644
--- a/internal/setup/enter.go
+++ b/internal/setup/enter.go
@@ -149,6 +149,7 @@ EnterForgeData:
 	featureRegex := None[configdomain.FeatureRegex]()
 	contributionRegex := None[configdomain.ContributionRegex]()
 	observedRegex := None[configdomain.ObservedRegex]()
+	order := None[configdomain.Order]()
 	newBranchType := None[configdomain.NewBranchType]()
 	unknownBranchType := None[configdomain.UnknownBranchType]()
 	syncFeatureStrategy := None[configdomain.SyncFeatureStrategy]()
@@ -240,6 +241,10 @@ EnterForgeData:
 		if err != nil || exit {
 			return emptyResult, exit, err
 		}
+		order, exit, err = enterOrder(data)
+		if err != nil || exit {
+			return emptyResult, exit, err
+		}
 	}
 	configStorage, exit, err := dialog.ConfigStorage(data.Inputs)
 	if err != nil || exit {
@@ -272,6 +277,7 @@ EnterForgeData:
 		NewBranchType:            newBranchType,
 		ObservedRegex:            observedRegex,
 		Offline:                  None[configdomain.Offline](), // the setup assistant doesn't ask for this
+		Order:                    order,
 		PerennialBranches:        perennialBranches,
 		PerennialRegex:           perennialRegex,
 		ProposalsShowLineage:     None[forgedomain.ProposalsShowLineage](), // TODO: populate this in the setup assistant once https://github.com/git-town/git-town/issues/3003 is shipped
@@ -515,6 +521,17 @@ func enterObservedRegex(data Data) (Option[configdomain.ObservedRegex], dialogdo
 	})
 }
 
+func enterOrder(data Data) (Option[configdomain.Order], dialogdomain.Exit, error) {
+	if data.Config.File.Order.IsSome() {
+		return None[configdomain.Order](), false, nil
+	}
+	return dialog.Order(dialog.Args[configdomain.Order]{
+		Global: data.Config.GitGlobal.Order,
+		Inputs: data.Inputs,
+		Local:  data.Config.GitLocal.Order,
+	})
+}
+
 func enterOriginHostName(data Data) (Option[configdomain.HostingOriginHostname], dialogdomain.Exit, error) {
 	if data.Config.File.HostingOriginHostname.IsSome() {
 		return None[configdomain.HostingOriginHostname](), false, nil
diff --git a/internal/setup/save.go b/internal/setup/save.go
index db4f3847e..7ec936c9e 100644
--- a/internal/setup/save.go
+++ b/internal/setup/save.go
@@ -107,6 +107,9 @@ func saveAllToFile(userInput UserInput, gitConfig configdomain.PartialConfig, ru
 	if gitConfig.ObservedRegex.IsSome() {
 		_ = gitconfig.RemoveObservedRegex(runner)
 	}
+	if gitConfig.Order.IsSome() {
+		_ = gitconfig.RemoveOrder(runner)
+	}
 	if len(gitConfig.PerennialBranches) > 0 {
 		_ = gitconfig.RemovePerennialBranches(runner)
 	}
@@ -231,6 +234,11 @@ func saveAllToGit(userInput UserInput, existingGitConfig configdomain.PartialCon
 			saveObservedRegex(userInput.Data.ObservedRegex, existingGitConfig.ObservedRegex, frontend),
 		)
 	}
+	if configFile.Order.IsNone() {
+		fc.Check(
+			saveOrder(userInput.Data.Order, existingGitConfig.Order, frontend),
+		)
+	}
 	if configFile.PushBranches.IsNone() {
 		fc.Check(
 			savePushBranches(userInput.Data.PushBranches, existingGitConfig.PushBranches, frontend),
@@ -481,6 +489,16 @@ func saveObservedRegex(valueToWriteToGit Option[configdomain.ObservedRegex], val
 	return nil
 }
 
+func saveOrder(valueToWriteToGit Option[configdomain.Order], valueAlreadyInGit Option[configdomain.Order], runner subshelldomain.Runner) error {
+	if valueAlreadyInGit.Equal(valueToWriteToGit) {
+		return nil
+	}
+	if value, has := valueToWriteToGit.Get(); has {
+		return gitconfig.SetOrder(runner, value, configdomain.ConfigScopeLocal)
+	}
+	return gitconfig.RemoveOrder(runner)
+}
+
 func saveOriginHostname(valueToWriteToGit Option[configdomain.HostingOriginHostname], valueAlreadyInGit Option[configdomain.HostingOriginHostname], frontend subshelldomain.Runner) error {
 	if valueToWriteToGit.Equal(valueAlreadyInGit) {
 		return nil
diff --git a/internal/vm/opcodes/branch_local_delete_content.go b/internal/vm/opcodes/branch_local_delete_content.go
index 23cfffcd9..6cc9cdc42 100644
--- a/internal/vm/opcodes/branch_local_delete_content.go
+++ b/internal/vm/opcodes/branch_local_delete_content.go
@@ -16,7 +16,7 @@ func (self *BranchLocalDeleteContent) Run(args shared.RunArgs) error {
 	switch args.Config.Value.NormalConfig.SyncFeatureStrategy {
 	case configdomain.SyncFeatureStrategyRebase:
 		opcodes := []shared.Opcode{}
-		descendents := args.Config.Value.NormalConfig.Lineage.Descendants(self.BranchToDelete)
+		descendents := args.Config.Value.NormalConfig.Lineage.Descendants(self.BranchToDelete, args.Config.Value.NormalConfig.Order)
 		if len(descendents) > 0 {
 			opcodes = append(opcodes, &RebaseOntoRemoveDeleted{
 				BranchToRebaseOnto: self.BranchToRebaseOnto,
diff --git a/internal/vm/opcodes/lineage_branch_remove.go b/internal/vm/opcodes/lineage_branch_remove.go
index 74ec644b1..a36048ba6 100644
--- a/internal/vm/opcodes/lineage_branch_remove.go
+++ b/internal/vm/opcodes/lineage_branch_remove.go
@@ -11,7 +11,7 @@ type LineageBranchRemove struct {
 
 func (self *LineageBranchRemove) Run(args shared.RunArgs) error {
 	parent, hasParent := args.Config.Value.NormalConfig.Lineage.Parent(self.Branch).Get()
-	children := args.Config.Value.NormalConfig.Lineage.Children(self.Branch)
+	children := args.Config.Value.NormalConfig.Lineage.Children(self.Branch, args.Config.Value.NormalConfig.Order)
 	for _, child := range children {
 		if !hasParent {
 			args.PrependOpcodes(&LineageParentRemove{Branch: child})
diff --git a/internal/vm/opcodes/proposal_update_lineage.go b/internal/vm/opcodes/proposal_update_lineage.go
index 84eacee78..7f13daf2c 100644
--- a/internal/vm/opcodes/proposal_update_lineage.go
+++ b/internal/vm/opcodes/proposal_update_lineage.go
@@ -32,6 +32,7 @@ func (self *ProposalUpdateLineage) Run(args shared.RunArgs) error {
 		CurrentBranch:            self.Current,
 		Lineage:                  args.Config.Value.NormalConfig.Lineage,
 		MainAndPerennialBranches: args.Config.Value.MainAndPerennials(),
+		Order:                    args.Config.Value.NormalConfig.Order,
 	}
 	builder, hasBuilder := forge.NewProposalStackLineageBuilder(lineageArgs, self.LineageTree).Get()
 	if !hasBuilder {
diff --git a/website/src/SUMMARY.md b/website/src/SUMMARY.md
index f2c138d41..6998b8e72 100644
--- a/website/src/SUMMARY.md
+++ b/website/src/SUMMARY.md
@@ -63,6 +63,7 @@
     - [Feature regex](preferences/feature-regex.md)
     - [Main branch](preferences/main-branch.md)
     - [Observed regex](preferences/observed-regex.md)
+    - [Order](preferences/order.md)
     - [Perennial branches](preferences/perennial-branches.md)
     - [Perennial regex](preferences/perennial-regex.md)
     - [Unknown branch type](preferences/unknown-branch-type.md)
diff --git a/website/src/preferences/order.md b/website/src/preferences/order.md
new file mode 100644
index 000000000..2feeddceb
--- /dev/null
+++ b/website/src/preferences/order.md
@@ -0,0 +1,38 @@
+# Order
+
+This setting allows you to change how Git Town orders branches it displays.
+
+Allowed values:
+
+- **asc** sort branches in natural sort order, ascending (default)
+- **desc** sort branches in natural sort order, descending
+
+## via CLI flag
+
+You can override this setting per command using:
+
+- `--order=asc` to force ascending order
+- `--order=desc` to force descending order
+
+## in config file
+
+```toml
+[branches]
+order = "<asc|desc>"
+```
+
+## in Git metadata
+
+To enable ordering branches in Git, run this command:
+
+```wrap
+git config [--global] git-town.order <asc|desc>
+```
+
+The optional `--global` flag applies this setting to all Git repositories on
+your machine. Without it, this setting applies to the current Git repo.
+
+## environment variable
+
+You can configure branches ordering by setting the `GIT_TOWN_ORDER` environment
+variable.



Test patch:
diff --git a/internal/cli/format/branch_lineage_test.go b/internal/cli/format/branch_lineage_test.go
index cf0773452..557f3aa42 100644
--- a/internal/cli/format/branch_lineage_test.go
+++ b/internal/cli/format/branch_lineage_test.go
@@ -14,7 +14,7 @@ func TestBranchLineage(t *testing.T) {
 	t.Run("empty", func(t *testing.T) {
 		t.Parallel()
 		lineage := configdomain.NewLineage()
-		have := format.BranchLineage(lineage)
+		have := format.BranchLineage(lineage, configdomain.OrderAsc)
 		want := ""
 		must.EqOp(t, want, have)
 	})
@@ -28,7 +28,7 @@ func TestBranchLineage(t *testing.T) {
 			"branch-2":  "main",
 			"hotfix":    "qa",
 		})
-		have := format.BranchLineage(lineage)
+		have := format.BranchLineage(lineage, configdomain.OrderAsc)
 		want := `
 main
   branch-1
@@ -49,7 +49,7 @@ qa
 			"branch-1B": "branch-1",
 			"branch-2":  "main",
 		})
-		have := format.BranchLineage(lineage)
+		have := format.BranchLineage(lineage, configdomain.OrderAsc)
 		want := `
 main
   branch-1
diff --git a/internal/config/configdomain/lineage_test.go b/internal/config/configdomain/lineage_test.go
index 01c3f03c7..c08f6c0ba 100644
--- a/internal/config/configdomain/lineage_test.go
+++ b/internal/config/configdomain/lineage_test.go
@@ -108,7 +108,7 @@ func TestLineage(t *testing.T) {
 		t.Run("only root exists", func(t *testing.T) {
 			t.Parallel()
 			lineage := configdomain.NewLineage()
-			have := lineage.BranchLineageWithoutRoot(main, gitdomain.LocalBranchNames{main})
+			have := lineage.BranchLineageWithoutRoot(main, gitdomain.LocalBranchNames{main}, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{}
 			must.Eq(t, want, have)
 		})
@@ -117,7 +117,7 @@ func TestLineage(t *testing.T) {
 			lineage := configdomain.NewLineageWith(configdomain.LineageData{
 				one: main,
 			})
-			have := lineage.BranchLineageWithoutRoot(one, gitdomain.LocalBranchNames{main})
+			have := lineage.BranchLineageWithoutRoot(one, gitdomain.LocalBranchNames{main}, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{one}
 			must.Eq(t, want, have)
 		})
@@ -130,24 +130,24 @@ func TestLineage(t *testing.T) {
 			want := gitdomain.LocalBranchNames{one, two}
 			t.Run("given root", func(t *testing.T) {
 				t.Parallel()
-				have := lineage.BranchLineageWithoutRoot(main, gitdomain.LocalBranchNames{main})
+				have := lineage.BranchLineageWithoutRoot(main, gitdomain.LocalBranchNames{main}, configdomain.OrderAsc)
 				must.Eq(t, want, have)
 			})
 			t.Run("given middle branch", func(t *testing.T) {
 				t.Parallel()
-				have := lineage.BranchLineageWithoutRoot(one, gitdomain.LocalBranchNames{main})
+				have := lineage.BranchLineageWithoutRoot(one, gitdomain.LocalBranchNames{main}, configdomain.OrderAsc)
 				must.Eq(t, want, have)
 			})
 			t.Run("given leaf branch", func(t *testing.T) {
 				t.Parallel()
-				have := lineage.BranchLineageWithoutRoot(two, gitdomain.LocalBranchNames{main})
+				have := lineage.BranchLineageWithoutRoot(two, gitdomain.LocalBranchNames{main}, configdomain.OrderAsc)
 				must.Eq(t, want, have)
 			})
 		})
 		t.Run("branch without an ancestor", func(t *testing.T) {
 			t.Parallel()
 			lineage := configdomain.NewLineage()
-			have := lineage.BranchLineageWithoutRoot(one, gitdomain.LocalBranchNames{main})
+			have := lineage.BranchLineageWithoutRoot(one, gitdomain.LocalBranchNames{main}, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{one}
 			must.Eq(t, want, have)
 		})
@@ -175,7 +175,7 @@ func TestLineage(t *testing.T) {
 				three: two,
 			})
 			give := gitdomain.LocalBranchNames{two, one}
-			have := lineage.BranchesAndAncestors(give)
+			have := lineage.BranchesAndAncestors(give, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{main, one, two}
 			must.Eq(t, want, have)
 		})
@@ -187,7 +187,7 @@ func TestLineage(t *testing.T) {
 				three: two,
 			})
 			give := gitdomain.LocalBranchNames{one, two, main, three}
-			have := lineage.BranchesAndAncestors(give)
+			have := lineage.BranchesAndAncestors(give, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{main, one, two, three}
 			must.Eq(t, want, have)
 		})
@@ -199,7 +199,7 @@ func TestLineage(t *testing.T) {
 				three: two,
 			})
 			give := gitdomain.LocalBranchNames{three}
-			have := lineage.BranchesAndAncestors(give)
+			have := lineage.BranchesAndAncestors(give, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{main, one, two, three}
 			must.Eq(t, want, have)
 		})
@@ -217,7 +217,7 @@ func TestLineage(t *testing.T) {
 				third:  second,
 			})
 			give := gitdomain.LocalBranchNames{main, first, one, second, third, three, two}
-			have := lineage.BranchesAndAncestors(give)
+			have := lineage.BranchesAndAncestors(give, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{main, first, second, third, one, two, three}
 			must.Eq(t, want, have)
 		})
@@ -246,7 +246,7 @@ func TestLineage(t *testing.T) {
 
 	t.Run("Children", func(t *testing.T) {
 		t.Parallel()
-		t.Run("provides all children of the given branch, ordered alphabetically", func(t *testing.T) {
+		t.Run("provides all children of the given branch, ordered ascending", func(t *testing.T) {
 			t.Parallel()
 			twoA := gitdomain.NewLocalBranchName("twoA")
 			twoB := gitdomain.NewLocalBranchName("twoB")
@@ -254,24 +254,36 @@ func TestLineage(t *testing.T) {
 				twoA: one,
 				twoB: one,
 			})
-			have := lineage.Children(one)
+			have := lineage.Children(one, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{twoA, twoB}
 			must.Eq(t, want, have)
 		})
+		t.Run("provides all children of the given branch, ordered descending", func(t *testing.T) {
+			t.Parallel()
+			twoA := gitdomain.NewLocalBranchName("twoA")
+			twoB := gitdomain.NewLocalBranchName("twoB")
+			lineage := configdomain.NewLineageWith(configdomain.LineageData{
+				twoA: one,
+				twoB: one,
+			})
+			have := lineage.Children(one, configdomain.OrderDesc)
+			want := gitdomain.LocalBranchNames{twoB, twoA}
+			must.Eq(t, want, have)
+		})
 		t.Run("provides only the immediate children, i.e. no grandchildren", func(t *testing.T) {
 			t.Parallel()
 			lineage := configdomain.NewLineageWith(configdomain.LineageData{
 				two:   one,
 				three: two,
 			})
-			have := lineage.Children(one)
+			have := lineage.Children(one, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{two}
 			must.Eq(t, want, have)
 		})
 		t.Run("empty", func(t *testing.T) {
 			t.Parallel()
 			lineage := configdomain.NewLineage()
-			have := lineage.Children(one)
+			have := lineage.Children(one, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{}
 			must.Eq(t, want, have)
 		})
@@ -287,7 +299,7 @@ func TestLineage(t *testing.T) {
 				branch: main,
 				other:  main,
 			})
-			have := lineage.Descendants(branch)
+			have := lineage.Descendants(branch, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{}
 			must.Eq(t, want, have)
 		})
@@ -303,7 +315,7 @@ func TestLineage(t *testing.T) {
 				child2: branch,
 				other:  main,
 			})
-			have := lineage.Descendants(branch)
+			have := lineage.Descendants(branch, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{child1, child2}
 			must.Eq(t, want, have)
 		})
@@ -327,7 +339,7 @@ func TestLineage(t *testing.T) {
 				child2b: child2,
 				other:   main,
 			})
-			have := lineage.Descendants(branch)
+			have := lineage.Descendants(branch, configdomain.OrderAsc)
 			want := gitdomain.LocalBranchNames{child1, child1a, child1b, child2, child2a, child2b}
 			must.Eq(t, want, have)
 		})
@@ -498,7 +510,10 @@ func TestLineage(t *testing.T) {
 			})
 			give := lineage.BranchNames()
 			want := gitdomain.LocalBranchNames{first, second, third, one, two, three}
-			have := lineage.OrderHierarchically(give)
+			have := lineage.OrderHierarchically(give, configdomain.OrderAsc)
+			must.Eq(t, want, have)
+			want = gitdomain.LocalBranchNames{one, two, three, first, second, third}
+			have = lineage.OrderHierarchically(give, configdomain.OrderDesc)
 			must.Eq(t, want, have)
 		})
 		t.Run("single lineage", func(t *testing.T) {
@@ -512,7 +527,7 @@ func TestLineage(t *testing.T) {
 			})
 			give := gitdomain.LocalBranchNames{four, one}
 			want := gitdomain.LocalBranchNames{one, four}
-			have := lineage.OrderHierarchically(give)
+			have := lineage.OrderHierarchically(give, configdomain.OrderAsc)
 			must.Eq(t, want, have)
 		})
 		t.Run("elements out of order", func(t *testing.T) {
@@ -524,7 +539,7 @@ func TestLineage(t *testing.T) {
 			})
 			give := gitdomain.LocalBranchNames{one, two, main, three}
 			want := gitdomain.LocalBranchNames{main, one, two, three}
-			have := lineage.OrderHierarchically(give)
+			have := lineage.OrderHierarchically(give, configdomain.OrderAsc)
 			must.Eq(t, want, have)
 		})
 		t.Run("perennial branches", func(t *testing.T) {
@@ -532,14 +547,14 @@ func TestLineage(t *testing.T) {
 			lineage := configdomain.NewLineage()
 			give := gitdomain.LocalBranchNames{one, two, main}
 			want := gitdomain.LocalBranchNames{main, one, two}
-			have := lineage.OrderHierarchically(give)
+			have := lineage.OrderHierarchically(give, configdomain.OrderAsc)
 			must.Eq(t, want, have)
 		})
 		t.Run("empty", func(t *testing.T) {
 			t.Parallel()
 			lineage := configdomain.NewLineage()
 			give := gitdomain.LocalBranchNames{}
-			have := lineage.OrderHierarchically(give)
+			have := lineage.OrderHierarchically(give, configdomain.OrderAsc)
 			must.Eq(t, 0, len(have))
 		})
 	})
diff --git a/internal/config/configfile/load_test.go b/internal/config/configfile/load_test.go
index ef413de15..9e1b94896 100644
--- a/internal/config/configfile/load_test.go
+++ b/internal/config/configfile/load_test.go
@@ -30,6 +30,7 @@ contribution-regex = "^gittown-"
 default-type = "contribution"
 feature-regex = "^kg-"
 observed-regex = "^dependabot\\/"
+order = "desc"
 perennials = [ "public", "staging" ]
 perennial-regex = "release-.*"
 unknown-type = "prototype"
@@ -74,6 +75,7 @@ prototype-branches = "compress"
 					FeatureRegex:      Ptr("^kg-"),
 					Main:              Ptr("main"),
 					ObservedRegex:     Ptr(`^dependabot\/`),
+					Order:             Ptr("desc"),
 					PerennialRegex:    Ptr("release-.*"),
 					Perennials:        []string{"public", "staging"},
 					UnknownType:       Ptr("prototype"),
diff --git a/internal/config/configfile/save_test.go b/internal/config/configfile/save_test.go
index 5fee094cc..1d7d8588e 100644
--- a/internal/config/configfile/save_test.go
+++ b/internal/config/configfile/save_test.go
@@ -52,6 +52,7 @@ func TestSave(t *testing.T) {
 				HostingOriginHostname:    configdomain.ParseHostingOriginHostname("forge"),
 				MainBranch:               Some(gitdomain.NewLocalBranchName("main")),
 				NewBranchType:            Some(configdomain.NewBranchType(configdomain.BranchTypePrototypeBranch)),
+				Order:                    Some(configdomain.OrderDesc),
 				PerennialBranches:        gitdomain.NewLocalBranchNames("qa", "staging"),
 				PerennialRegex:           asserts.NoError1(configdomain.ParsePerennialRegex("perennial-")),
 				ProposalsShowLineage:     Some(forgedomain.ProposalsShowLineageCLI),
@@ -70,6 +71,7 @@ func TestSave(t *testing.T) {
 
 [branches]
 main = "main"
+order = "desc"
 perennials = ["qa", "staging"]
 perennial-regex = "perennial-"
 
diff --git a/internal/gohacks/slice/natural_sort_reverse_test.go b/internal/gohacks/slice/natural_sort_reverse_test.go
new file mode 100644
index 000000000..1e0a48d04
--- /dev/null
+++ b/internal/gohacks/slice/natural_sort_reverse_test.go
@@ -0,0 +1,22 @@
+package slice_test
+
+import (
+	"testing"
+
+	"github.com/git-town/git-town/v22/internal/gohacks/slice"
+	"github.com/shoenig/test/must"
+)
+
+func TestNaturalSortReverse(t *testing.T) {
+	t.Parallel()
+	tests := map[*[]stringer]*[]stringer{
+		{}:                       {},                       // empty
+		{"1"}:                    {"1"},                    // single element
+		{"a3c", "a20b", "a100a"}: {"a100a", "a20b", "a3c"}, // ordering by numeric value (reversed)
+		{"a10b2", "a10b10"}:      {"a10b10", "a10b2"},      // multiple parts of numbers and characters (reversed)
+	}
+	for give, want := range tests {
+		slice.NaturalSortReverse(*give)
+		must.Eq(t, want, give)
+	}
+}
diff --git a/internal/test/commands/test_commands.go b/internal/test/commands/test_commands.go
index 72045d88a..0dcc2ac14 100644
--- a/internal/test/commands/test_commands.go
+++ b/internal/test/commands/test_commands.go
@@ -95,11 +95,11 @@ func (self *TestCommands) CommitStagedChanges(message gitdomain.CommitMessage) {
 }
 
 // Commits provides a list of the commits in this Git repository with the given fields.
-func (self *TestCommands) Commits(fields []string, lineage configdomain.Lineage) []testgit.Commit {
+func (self *TestCommands) Commits(fields []string, lineage configdomain.Lineage, order configdomain.Order) []testgit.Commit {
 	// NOTE: This method uses the provided lineage instead of self.Config.NormalConfig.Lineage
 	//       because it might determine the commits on a remote repo, and that repo has no lineage information.
 	//       We therefore always provide the lineage of the local repo.
-	branches, branchesInOtherWorktree := asserts.NoError2(self.LocalBranchesOrderedHierarchically(lineage))
+	branches, branchesInOtherWorktree := asserts.NoError2(self.LocalBranchesOrderedHierarchically(lineage, order))
 	var result []testgit.Commit
 	for _, branch := range branches {
 		if slices.Contains(branchesInOtherWorktree, branch) {
@@ -418,7 +418,7 @@ func (self *TestCommands) Lineage() configdomain.Lineage {
 }
 
 func (self *TestCommands) LineageText(lineage configdomain.Lineage) string {
-	return format.BranchLineage(lineage)
+	return format.BranchLineage(lineage, configdomain.OrderAsc)
 }
 
 // LocalBranches provides the names of all branches in the local repository,
@@ -474,12 +474,12 @@ func (self *TestCommands) LocalBranchesMainFirst(mainBranch gitdomain.LocalBranc
 }
 
 // LocalBranchesMainFirst provides the names of all local branches in this repo.
-func (self *TestCommands) LocalBranchesOrderedHierarchically(lineage configdomain.Lineage) (allBranches, branchesInOtherWorktrees gitdomain.LocalBranchNames, err error) {
+func (self *TestCommands) LocalBranchesOrderedHierarchically(lineage configdomain.Lineage, order configdomain.Order) (allBranches, branchesInOtherWorktrees gitdomain.LocalBranchNames, err error) {
 	allBranches, branchesInOtherWorktrees, err = self.LocalBranches()
 	if err != nil {
 		return
 	}
-	allBranches = lineage.OrderHierarchically(allBranches)
+	allBranches = lineage.OrderHierarchically(allBranches, order)
 	return
 }
 
diff --git a/internal/test/commands/test_commands_test.go b/internal/test/commands/test_commands_test.go
index c288f6396..d375795f6 100644
--- a/internal/test/commands/test_commands_test.go
+++ b/internal/test/commands/test_commands_test.go
@@ -48,7 +48,7 @@ func TestTestCommands(t *testing.T) {
 			FileName:    "file2",
 			Message:     "second commit",
 		})
-		commits := runtime.Commits([]string{"FILE NAME", "FILE CONTENT"}, runtime.Config.NormalConfig.Lineage)
+		commits := runtime.Commits([]string{"FILE NAME", "FILE CONTENT"}, runtime.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 		must.Len(t, 2, commits)
 		must.EqOp(t, "initial", commits[0].Branch)
 		must.EqOp(t, "file1", commits[0].FileName)
@@ -110,7 +110,7 @@ func TestTestCommands(t *testing.T) {
 				FileName:    "hello.txt",
 				Message:     "test commit",
 			})
-			commits := runtime.Commits([]string{"FILE NAME", "FILE CONTENT"}, runtime.Config.NormalConfig.Lineage)
+			commits := runtime.Commits([]string{"FILE NAME", "FILE CONTENT"}, runtime.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 			must.Len(t, 1, commits)
 			must.EqOp(t, "hello.txt", commits[0].FileName)
 			must.EqOp(t, "hello world", commits[0].FileContent)
@@ -128,7 +128,7 @@ func TestTestCommands(t *testing.T) {
 				FileName:    "hello.txt",
 				Message:     "test commit",
 			})
-			commits := runtime.Commits([]string{"FILE NAME", "FILE CONTENT"}, runtime.Config.NormalConfig.Lineage)
+			commits := runtime.Commits([]string{"FILE NAME", "FILE CONTENT"}, runtime.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 			must.Len(t, 1, commits)
 			must.EqOp(t, "hello.txt", commits[0].FileName)
 			must.EqOp(t, "hello world", commits[0].FileContent)
@@ -208,7 +208,7 @@ func TestTestCommands(t *testing.T) {
 		runtime.CreateFile("f2.txt", "two")
 		runtime.StageFiles("f1.txt", "f2.txt")
 		runtime.CommitStagedChanges("stuff")
-		commits := runtime.Commits([]string{}, runtime.Config.NormalConfig.Lineage)
+		commits := runtime.Commits([]string{}, runtime.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 		must.Len(t, 2, commits)
 		fileNames := runtime.FilesInCommit(commits[1].SHA)
 		must.Eq(t, []string{"f1.txt", "f2.txt"}, fileNames)
diff --git a/internal/test/cucumber/steps.go b/internal/test/cucumber/steps.go
index 19a5f326f..ebb184aaf 100644
--- a/internal/test/cucumber/steps.go
+++ b/internal/test/cucumber/steps.go
@@ -1530,7 +1530,7 @@ func defineSteps(sc *godog.ScenarioContext) {
 	sc.Step(`^this lineage exists now$`, func(ctx context.Context, want *godog.DocString) error {
 		state := ctx.Value(keyScenarioState).(*ScenarioState)
 		devRepo := state.fixture.DevRepo.GetOrPanic()
-		have := format.BranchLineage(devRepo.Config.NormalConfig.Lineage)
+		have := format.BranchLineage(devRepo.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 		if have != want.Content {
 			fmt.Println("WANT:\n" + want.Content)
 			fmt.Println("HAVE:\n" + have)
diff --git a/internal/test/datatable/commit_table_builder.go b/internal/test/datatable/commit_table_builder.go
index f2b225f69..a6ab8e27b 100644
--- a/internal/test/datatable/commit_table_builder.go
+++ b/internal/test/datatable/commit_table_builder.go
@@ -69,12 +69,12 @@ func (self *CommitTableBuilder) AddMany(commits []testgit.Commit, location strin
 }
 
 // Table provides the data accumulated by this CommitTableBuilder as a DataTable.
-func (self *CommitTableBuilder) Table(fields []string, lineage configdomain.Lineage) DataTable {
+func (self *CommitTableBuilder) Table(fields []string, lineage configdomain.Lineage, order configdomain.Order) DataTable {
 	result := DataTable{}
 	result.AddRow(fields...)
 	var lastBranch gitdomain.LocalBranchName
 	lastLocation := ""
-	for _, branch := range self.branches(lineage) {
+	for _, branch := range self.branches(lineage, order) {
 		SHAs := self.commitsInBranch[branch]
 		for _, SHA := range SHAs.Elements() {
 			commit := self.commits[SHA]
@@ -116,7 +116,7 @@ func (self *CommitTableBuilder) Table(fields []string, lineage configdomain.Line
 
 // branches provides the names of the all branches known to this CommitTableBuilder,
 // sorted alphabetically, with the main branch first.
-func (self *CommitTableBuilder) branches(lineage configdomain.Lineage) gitdomain.LocalBranchNames {
+func (self *CommitTableBuilder) branches(lineage configdomain.Lineage, order configdomain.Order) gitdomain.LocalBranchNames {
 	result := make(gitdomain.LocalBranchNames, 0, len(self.commitsInBranch))
 	hasMain := false
 	for branch := range mapstools.SortedKeys(self.commitsInBranch) {
@@ -126,7 +126,7 @@ func (self *CommitTableBuilder) branches(lineage configdomain.Lineage) gitdomain
 			result = append(result, branch)
 		}
 	}
-	result = lineage.OrderHierarchically(result)
+	result = lineage.OrderHierarchically(result, order)
 	if hasMain {
 		return append(gitdomain.NewLocalBranchNames("main"), result...)
 	}
diff --git a/internal/test/datatable/commit_table_builder_test.go b/internal/test/datatable/commit_table_builder_test.go
index 6a6a5027b..3ac923259 100644
--- a/internal/test/datatable/commit_table_builder_test.go
+++ b/internal/test/datatable/commit_table_builder_test.go
@@ -30,7 +30,7 @@ func TestCommitTableBuilder(t *testing.T) {
 		"branch2": "branch1",
 		"branch3": "branch2",
 	})
-	table := builder.Table([]string{"BRANCH", "LOCATION", "MESSAGE"}, lineage)
+	table := builder.Table([]string{"BRANCH", "LOCATION", "MESSAGE"}, lineage, configdomain.OrderAsc)
 	expected := `
 | BRANCH  | LOCATION                | MESSAGE |
 | main    | local                   | commit2 |
diff --git a/internal/test/fixture/fixture.go b/internal/test/fixture/fixture.go
index f95dded01..adf88d81d 100644
--- a/internal/test/fixture/fixture.go
+++ b/internal/test/fixture/fixture.go
@@ -129,25 +129,25 @@ func (self *Fixture) Branches() datatable.DataTable {
 func (self *Fixture) CommitTable(fields []string) datatable.DataTable {
 	builder := datatable.NewCommitTableBuilder()
 	lineage := self.DevRepo.Value.Config.NormalConfig.Lineage
-	localCommits := self.DevRepo.GetOrPanic().Commits(fields, lineage)
+	localCommits := self.DevRepo.GetOrPanic().Commits(fields, lineage, configdomain.OrderAsc)
 	builder.AddMany(localCommits, "local")
 	if coworkerRepo, hasCoworkerRepo := self.CoworkerRepo.Get(); hasCoworkerRepo {
-		coworkerCommits := coworkerRepo.Commits(fields, lineage)
+		coworkerCommits := coworkerRepo.Commits(fields, lineage, configdomain.OrderAsc)
 		builder.AddMany(coworkerCommits, "coworker")
 	}
 	if originRepo, hasOriginRepo := self.OriginRepo.Get(); hasOriginRepo {
-		originCommits := originRepo.Commits(fields, lineage)
+		originCommits := originRepo.Commits(fields, lineage, configdomain.OrderAsc)
 		builder.AddMany(originCommits, self.DevRepo.Value.Config.NormalConfig.DevRemote.String())
 	}
 	if upstreamRepo, hasUpstreamRepo := self.UpstreamRepo.Get(); hasUpstreamRepo {
-		upstreamCommits := upstreamRepo.Commits(fields, lineage)
+		upstreamCommits := upstreamRepo.Commits(fields, lineage, configdomain.OrderAsc)
 		builder.AddMany(upstreamCommits, "upstream")
 	}
 	if secondWorkTree, hasSecondWorkTree := self.SecondWorktree.Get(); hasSecondWorkTree {
-		secondWorktreeCommits := secondWorkTree.Commits(fields, lineage)
+		secondWorktreeCommits := secondWorkTree.Commits(fields, lineage, configdomain.OrderAsc)
 		builder.AddMany(secondWorktreeCommits, "worktree")
 	}
-	return builder.Table(fields, lineage)
+	return builder.Table(fields, lineage, configdomain.OrderAsc)
 }
 
 // CreateCommits creates the commits described by the given Gherkin table in this Git repository.
diff --git a/internal/test/fixture/fixture_test.go b/internal/test/fixture/fixture_test.go
index 5bc99b051..5dc0d456c 100644
--- a/internal/test/fixture/fixture_test.go
+++ b/internal/test/fixture/fixture_test.go
@@ -5,6 +5,7 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/git-town/git-town/v22/internal/config/configdomain"
 	"github.com/git-town/git-town/v22/internal/git/gitdomain"
 	"github.com/git-town/git-town/v22/internal/test/fixture"
 	"github.com/git-town/git-town/v22/internal/test/testgit"
@@ -165,7 +166,7 @@ func TestFixture(t *testing.T) {
 			},
 		})
 		// verify local commits
-		commits := cloned.DevRepo.GetOrPanic().Commits([]string{"FILE NAME", "FILE CONTENT"}, cloned.DevRepo.Value.Config.NormalConfig.Lineage)
+		commits := cloned.DevRepo.GetOrPanic().Commits([]string{"FILE NAME", "FILE CONTENT"}, cloned.DevRepo.Value.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 		must.Len(t, 2, commits)
 		must.EqOp(t, "local and origin commit", commits[0].Message)
 		must.EqOp(t, "loc-rem-file", commits[0].FileName)
@@ -174,7 +175,7 @@ func TestFixture(t *testing.T) {
 		must.EqOp(t, "local-file", commits[1].FileName)
 		must.EqOp(t, "local content", commits[1].FileContent)
 		// verify origin commits
-		commits = cloned.OriginRepo.GetOrPanic().Commits([]string{"FILE NAME", "FILE CONTENT"}, cloned.DevRepo.Value.Config.NormalConfig.Lineage)
+		commits = cloned.OriginRepo.GetOrPanic().Commits([]string{"FILE NAME", "FILE CONTENT"}, cloned.DevRepo.Value.Config.NormalConfig.Lineage, configdomain.OrderAsc)
 		must.Len(t, 2, commits)
 		must.EqOp(t, "local and origin commit", commits[0].Message)
 		must.EqOp(t, "loc-rem-file", commits[0].FileName)