Repository: jesseduffield/lazygit (language: go)

Issue:
sub-commands or flags into specific layout directly
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

When I start lazygit inside terminal, most of time I need to jump into a specific layout such `git status` or `git log` directly, right now, the default layout is all the panes/windows are displayed, and default focused pane is `Files` (git status), I have to manually switch to other panes using `1-5`, `tig` provides subcommand such `tig status` or `tig log` to display `git status` or `git log` directly.

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

Provide subcommands such as `status`, `log`, `stash` or flags like tig to start lazygit with a specific pane, the startup layout would be like, switch to the specific pane using `1-5` and then `+`(next screen mode)

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion / hints:
I'm in favour

I'd like to tackle this issue!
@HiromasaNojima awesome! A good starting point would be in `main.go` to parse the subcommand, then you'd want to pass that through the app struct to the gui struct (similar to how we do with the `filterPath` argument, and then you'd want to pass it into the `resetState` method in `pkg/gui/gui.go`, where if the user has started lazygit in the specific mode, we set `screenMode` to `SCREEN_HALF` and `initialContext` to whatever the appropriate context is.

You'll notice within that function that we already have:
```go
	screenMode := SCREEN_NORMAL
	var initialContext types.IListContext = contextTree.Files
	if filterPath != "" {
		screenMode = SCREEN_HALF
		initialContext = contextTree.LocalCommits
	}
```
in that function so we'd be doing something similar
@jesseduffield Thank you for the helpful advice!
Looks very good. Thank you. :heart: 

Fix patch:
diff --git a/main.go b/main.go
index c1de86e3089..3a0f0714805 100644
--- a/main.go
+++ b/main.go
@@ -7,12 +7,14 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 
 	"github.com/integrii/flaggy"
 	"github.com/jesseduffield/lazygit/pkg/app"
 	"github.com/jesseduffield/lazygit/pkg/app/daemon"
 	"github.com/jesseduffield/lazygit/pkg/config"
 	"github.com/jesseduffield/lazygit/pkg/env"
+	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/jesseduffield/lazygit/pkg/logs"
 	yaml "github.com/jesseduffield/yaml"
 )
@@ -33,9 +35,8 @@ func main() {
 	filterPath := ""
 	flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
 
-	dump := ""
-	flaggy.AddPositionalValue(&dump, "gitargs", 1, false, "Todo file")
-	flaggy.DefaultParser.PositionalFlags[0].Hidden = true
+	gitArg := ""
+	flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.")
 
 	versionFlag := false
 	flaggy.Bool(&versionFlag, "v", "version", "Print the current version")
@@ -148,5 +149,31 @@ func main() {
 		return
 	}
 
-	app.Run(appConfig, common, filterPath)
+	parsedGitArg := parseGitArg(gitArg)
+
+	app.Run(appConfig, common, types.NewStartArgs(filterPath, parsedGitArg))
+}
+
+func parseGitArg(gitArg string) types.GitArg {
+	typedArg := types.GitArg(gitArg)
+
+	// using switch so that linter catches when a new git arg value is defined but not handled here
+	switch typedArg {
+	case types.GitArgNone, types.GitArgStatus, types.GitArgBranch, types.GitArgLog, types.GitArgStash:
+		return typedArg
+	}
+
+	permittedValues := []string{
+		string(types.GitArgStatus),
+		string(types.GitArgBranch),
+		string(types.GitArgLog),
+		string(types.GitArgStash),
+	}
+
+	log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.",
+		gitArg,
+		strings.Join(permittedValues, ", "),
+	)
+
+	panic("unreachable")
 }
diff --git a/pkg/app/app.go b/pkg/app/app.go
index f12c0f08ad5..e02c88339c4 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -22,6 +22,7 @@ import (
 	"github.com/jesseduffield/lazygit/pkg/constants"
 	"github.com/jesseduffield/lazygit/pkg/env"
 	"github.com/jesseduffield/lazygit/pkg/gui"
+	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/jesseduffield/lazygit/pkg/i18n"
 	"github.com/jesseduffield/lazygit/pkg/updates"
 )
@@ -37,11 +38,11 @@ type App struct {
 	Updater   *updates.Updater // may only need this on the Gui
 }
 
-func Run(config config.AppConfigurer, common *common.Common, filterPath string) {
+func Run(config config.AppConfigurer, common *common.Common, startArgs types.StartArgs) {
 	app, err := NewApp(config, common)
 
 	if err == nil {
-		err = app.Run(filterPath)
+		err = app.Run(startArgs)
 	}
 
 	if err != nil {
@@ -203,8 +204,8 @@ func (app *App) setupRepo() (bool, error) {
 	return false, nil
 }
 
-func (app *App) Run(filterPath string) error {
-	err := app.Gui.RunAndHandleError(filterPath)
+func (app *App) Run(startArgs types.StartArgs) error {
+	err := app.Gui.RunAndHandleError(startArgs)
 	return err
 }
 
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 3f07fb98612..531a430352c 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -249,7 +249,7 @@ type guiMutexes struct {
 	PopupMutex            *sync.Mutex
 }
 
-func (gui *Gui) onNewRepo(filterPath string, reuseState bool) error {
+func (gui *Gui) onNewRepo(startArgs types.StartArgs, reuseState bool) error {
 	var err error
 	gui.git, err = commands.NewGitCommand(
 		gui.Common,
@@ -261,7 +261,7 @@ func (gui *Gui) onNewRepo(filterPath string, reuseState bool) error {
 		return err
 	}
 
-	gui.resetState(filterPath, reuseState)
+	gui.resetState(startArgs, reuseState)
 
 	gui.resetControllers()
 
@@ -281,7 +281,7 @@ func (gui *Gui) onNewRepo(filterPath string, reuseState bool) error {
 // it gets a bit confusing to land back in the status panel when visiting a repo
 // you've already switched from. There's no doubt some easy way to make the UX
 // optimal for all cases but I'm too lazy to think about what that is right now
-func (gui *Gui) resetState(filterPath string, reuseState bool) {
+func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) {
 	currentDir, err := os.Getwd()
 
 	if reuseState {
@@ -306,12 +306,8 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
 
 	contextTree := gui.contextTree()
 
-	screenMode := SCREEN_NORMAL
-	var initialContext types.IListContext = contextTree.Files
-	if filterPath != "" {
-		screenMode = SCREEN_HALF
-		initialContext = contextTree.LocalCommits
-	}
+	initialContext := initialContext(contextTree, startArgs)
+	initialScreenMode := initialScreenMode(startArgs)
 
 	viewContextMap := context.NewViewContextMap()
 	for viewName, context := range initialViewContextMapping(contextTree) {
@@ -338,13 +334,13 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
 		},
 		Ptmx: nil,
 		Modes: &types.Modes{
-			Filtering:     filtering.New(filterPath),
+			Filtering:     filtering.New(startArgs.FilterPath),
 			CherryPicking: cherrypicking.New(),
 			Diffing:       diffing.New(),
 		},
 		ViewContextMap:    viewContextMap,
 		ViewTabContextMap: gui.initialViewTabContextMap(contextTree),
-		ScreenMode:        screenMode,
+		ScreenMode:        initialScreenMode,
 		// TODO: put contexts in the context manager
 		ContextManager: NewContextManager(initialContext),
 		Contexts:       contextTree,
@@ -355,6 +351,37 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
 	gui.RepoStateMap[Repo(currentDir)] = gui.State
 }
 
+func initialScreenMode(startArgs types.StartArgs) WindowMaximisation {
+	if startArgs.FilterPath != "" || startArgs.GitArg != types.GitArgNone {
+		return SCREEN_HALF
+	} else {
+		return SCREEN_NORMAL
+	}
+}
+
+func initialContext(contextTree *context.ContextTree, startArgs types.StartArgs) types.IListContext {
+	var initialContext types.IListContext = contextTree.Files
+
+	if startArgs.FilterPath != "" {
+		initialContext = contextTree.LocalCommits
+	} else if startArgs.GitArg != types.GitArgNone {
+		switch startArgs.GitArg {
+		case types.GitArgStatus:
+			initialContext = contextTree.Files
+		case types.GitArgBranch:
+			initialContext = contextTree.Branches
+		case types.GitArgLog:
+			initialContext = contextTree.LocalCommits
+		case types.GitArgStash:
+			initialContext = contextTree.Stash
+		default:
+			panic("unhandled git arg")
+		}
+	}
+
+	return initialContext
+}
+
 func (gui *Gui) syncViewContexts() {
 	for viewName, context := range gui.State.ViewContextMap.Entries() {
 		view, err := gui.g.View(viewName)
@@ -506,7 +533,7 @@ func (gui *Gui) initialViewTabContextMap(contextTree *context.ContextTree) map[s
 }
 
 // Run: setup the gui with keybindings and start the mainloop
-func (gui *Gui) Run(filterPath string) error {
+func (gui *Gui) Run(startArgs types.StartArgs) error {
 	g, err := gui.initGocui()
 	if err != nil {
 		return err
@@ -559,7 +586,7 @@ func (gui *Gui) Run(filterPath string) error {
 	}
 
 	// onNewRepo must be called after g.SetManager because SetManager deletes keybindings
-	if err := gui.onNewRepo(filterPath, false); err != nil {
+	if err := gui.onNewRepo(startArgs, false); err != nil {
 		return err
 	}
 
@@ -592,10 +619,10 @@ func (gui *Gui) Run(filterPath string) error {
 	return gui.g.MainLoop()
 }
 
-func (gui *Gui) RunAndHandleError(filterPath string) error {
+func (gui *Gui) RunAndHandleError(startArgs types.StartArgs) error {
 	gui.stopChan = make(chan struct{})
 	return utils.SafeWithError(func() error {
-		if err := gui.Run(filterPath); err != nil {
+		if err := gui.Run(startArgs); err != nil {
 			for _, manager := range gui.viewBufferManagerMap {
 				manager.Close()
 			}
diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go
index dc2f9f17f61..6b5a0b73d8e 100644
--- a/pkg/gui/recent_repos_panel.go
+++ b/pkg/gui/recent_repos_panel.go
@@ -79,7 +79,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
 	gui.Mutexes.RefreshingFilesMutex.Lock()
 	defer gui.Mutexes.RefreshingFilesMutex.Unlock()
 
-	return gui.onNewRepo("", reuse)
+	return gui.onNewRepo(types.StartArgs{}, reuse)
 }
 
 // updateRecentRepoList registers the fact that we opened lazygit in this repo,
diff --git a/pkg/gui/types/main_args.go b/pkg/gui/types/main_args.go
new file mode 100644
index 00000000000..b055b373648
--- /dev/null
+++ b/pkg/gui/types/main_args.go
@@ -0,0 +1,26 @@
+package types
+
+// StartArgs is the struct that represents some things we want to do on program start
+type StartArgs struct {
+	// FilterPath determines which path we're going to filter on so that we only see commits from that file.
+	FilterPath string
+	// GitArg determines what context we open in
+	GitArg GitArg
+}
+
+type GitArg string
+
+const (
+	GitArgNone   GitArg = ""
+	GitArgStatus GitArg = "status"
+	GitArgBranch GitArg = "branch"
+	GitArgLog    GitArg = "log"
+	GitArgStash  GitArg = "stash"
+)
+
+func NewStartArgs(filterPath string, gitArg GitArg) StartArgs {
+	return StartArgs{
+		FilterPath: filterPath,
+		GitArg:     gitArg,
+	}
+}


Test patch:
diff --git a/test.sh b/test.sh
index 0a8d91e85e9..d95a9b76ee3 100755
--- a/test.sh
+++ b/test.sh
@@ -12,7 +12,7 @@ fi
 
 for d in $( find ./* -maxdepth 10 ! -path "./vendor*" ! -path "./.git*" ! -path "./scripts*" -type d); do
     if ls $d/*.go &> /dev/null; then
-        args="-race -coverprofile=profile.out -covermode=atomic $d"
+        args="-race -v -coverprofile=profile.out -covermode=atomic $d"
         if [ "$use_go_test" == true ]; then
             gotest $args
         else
diff --git a/test/integration/gitArg/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration/gitArg/expected/repo/.git_keep/COMMIT_EDITMSG
new file mode 100644
index 00000000000..907b308167f
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/COMMIT_EDITMSG
@@ -0,0 +1,1 @@
+blah
diff --git a/test/integration/gitArg/expected/repo/.git_keep/FETCH_HEAD b/test/integration/gitArg/expected/repo/.git_keep/FETCH_HEAD
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/integration/gitArg/expected/repo/.git_keep/HEAD b/test/integration/gitArg/expected/repo/.git_keep/HEAD
new file mode 100644
index 00000000000..cb089cd89a7
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/HEAD
@@ -0,0 +1,1 @@
+ref: refs/heads/master
diff --git a/test/integration/gitArg/expected/repo/.git_keep/config b/test/integration/gitArg/expected/repo/.git_keep/config
new file mode 100644
index 00000000000..8ae104545b4
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/config
@@ -0,0 +1,10 @@
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = false
+	logallrefupdates = true
+	ignorecase = true
+	precomposeunicode = true
+[user]
+	email = CI@example.com
+	name = CI
diff --git a/test/integration/gitArg/expected/repo/.git_keep/description b/test/integration/gitArg/expected/repo/.git_keep/description
new file mode 100644
index 00000000000..498b267a8c7
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/description
@@ -0,0 +1,1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/test/integration/gitArg/expected/repo/.git_keep/index b/test/integration/gitArg/expected/repo/.git_keep/index
new file mode 100644
index 00000000000..65d675154f2
Binary files /dev/null and b/test/integration/gitArg/expected/repo/.git_keep/index differ
diff --git a/test/integration/gitArg/expected/repo/.git_keep/info/exclude b/test/integration/gitArg/expected/repo/.git_keep/info/exclude
new file mode 100644
index 00000000000..8e9f2071f5d
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/info/exclude
@@ -0,0 +1,7 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+.DS_Store
diff --git a/test/integration/gitArg/expected/repo/.git_keep/logs/HEAD b/test/integration/gitArg/expected/repo/.git_keep/logs/HEAD
new file mode 100644
index 00000000000..eaac3a34d87
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/logs/HEAD
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 CI <CI@example.com> 1654768290 +1000	commit (initial): blah
+45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 CI <CI@example.com> 1654768290 +1000	checkout: moving from master to other
+45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 CI <CI@example.com> 1654768291 +1000	checkout: moving from other to master
diff --git a/test/integration/gitArg/expected/repo/.git_keep/logs/refs/heads/master b/test/integration/gitArg/expected/repo/.git_keep/logs/refs/heads/master
new file mode 100644
index 00000000000..12b2c7ee444
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/logs/refs/heads/master
@@ -0,0 +1,1 @@
+0000000000000000000000000000000000000000 45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 CI <CI@example.com> 1654768290 +1000	commit (initial): blah
diff --git a/test/integration/gitArg/expected/repo/.git_keep/logs/refs/heads/other b/test/integration/gitArg/expected/repo/.git_keep/logs/refs/heads/other
new file mode 100644
index 00000000000..d1b9aa145ef
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/logs/refs/heads/other
@@ -0,0 +1,1 @@
+0000000000000000000000000000000000000000 45fe0608335366a31a1ad6dacbdcc6b17d31a5b6 CI <CI@example.com> 1654768290 +1000	branch: Created from HEAD
diff --git a/test/integration/gitArg/expected/repo/.git_keep/objects/45/fe0608335366a31a1ad6dacbdcc6b17d31a5b6 b/test/integration/gitArg/expected/repo/.git_keep/objects/45/fe0608335366a31a1ad6dacbdcc6b17d31a5b6
new file mode 100644
index 00000000000..3171fbc5aa4
Binary files /dev/null and b/test/integration/gitArg/expected/repo/.git_keep/objects/45/fe0608335366a31a1ad6dacbdcc6b17d31a5b6 differ
diff --git a/test/integration/gitArg/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/test/integration/gitArg/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 00000000000..adf64119a33
Binary files /dev/null and b/test/integration/gitArg/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ
diff --git a/test/integration/gitArg/expected/repo/.git_keep/refs/heads/master b/test/integration/gitArg/expected/repo/.git_keep/refs/heads/master
new file mode 100644
index 00000000000..86a280f5259
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/refs/heads/master
@@ -0,0 +1,1 @@
+45fe0608335366a31a1ad6dacbdcc6b17d31a5b6
diff --git a/test/integration/gitArg/expected/repo/.git_keep/refs/heads/other b/test/integration/gitArg/expected/repo/.git_keep/refs/heads/other
new file mode 100644
index 00000000000..86a280f5259
--- /dev/null
+++ b/test/integration/gitArg/expected/repo/.git_keep/refs/heads/other
@@ -0,0 +1,1 @@
+45fe0608335366a31a1ad6dacbdcc6b17d31a5b6
diff --git a/test/integration/gitArg/recording.json b/test/integration/gitArg/recording.json
new file mode 100644
index 00000000000..0a42da3cf2c
--- /dev/null
+++ b/test/integration/gitArg/recording.json
@@ -0,0 +1,1 @@
+{"KeyEvents":[{"Timestamp":620,"Mod":0,"Key":258,"Ch":0},{"Timestamp":995,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1865,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}
\ No newline at end of file
diff --git a/test/integration/gitArg/setup.sh b/test/integration/gitArg/setup.sh
new file mode 100644
index 00000000000..d71cc83e479
--- /dev/null
+++ b/test/integration/gitArg/setup.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -e
+
+cd $1
+
+git init
+
+git config user.email "CI@example.com"
+git config user.name "CI"
+
+git commit --allow-empty -m "blah"
+
+git checkout -b other
diff --git a/test/integration/gitArg/test.json b/test/integration/gitArg/test.json
new file mode 100644
index 00000000000..0261df3ea54
--- /dev/null
+++ b/test/integration/gitArg/test.json
@@ -0,0 +1,5 @@
+{
+  "description": "Open lazygit to the branches panel",
+  "speed": 10,
+  "extraCmdArgs": "branch"
+}