Repository: jesseduffield/lazygit (language: go)

Issue:
Add "CopyCommitURLToClipboard" option
I'll take all the pointers I can get, please.

This closes #1768 .


Discussion / hints:
Code looks good, my only hesitation is around keybinding realestate: there's quite a few things you might want to copy from a commit:
* SHA
* message
* url
* timestamp (contrived I know)

I wonder if we should be putting these copy-to-clipboard options behind a menu, rather than finding a new keybinding for each new use case. What do you think?
_I concur._

I'd only pitch in `commit diff` into that pool, so I can "export" it as a patch.
@mark2185 that's a good idea, I hadn't thought of that
@jesseduffield except that the `diff` isn't parsed from the `git log`. If I'm looking in [the right place](https://github.com/jesseduffield/lazygit/blob/5ded030a884b6358da34ddd5b855e0f2cb005d24/pkg/commands/loaders/commits.go#L158-L159).

EDIT: Nevermind, found [this](https://github.com/jesseduffield/lazygit/blob/5ded030a884b6358da34ddd5b855e0f2cb005d24/pkg/commands/git_commands/commit.go#L70) file.

Fix patch:
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index 6f8c129662b..836bbf791f2 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -90,8 +90,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: copy commit SHA to clipboard
   <kbd>space</kbd>: checkout commit
-  <kbd>g</kbd>: view reset options
-  <kbd>n</kbd>: new branch
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: create new branch off of commit
+  <kbd>g</kbd>: reset to this commit
   <kbd>c</kbd>: copy commit (cherry-pick)
   <kbd>C</kbd>: copy commit range (cherry-pick)
   <kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
@@ -147,16 +149,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   <kbd>ctrl+k</kbd>: move commit up one
   <kbd>A</kbd>: amend commit with staged changes
   <kbd>t</kbd>: revert commit
-  <kbd>n</kbd>: create new branch off of commit
-  <kbd>c</kbd>: copy commit (cherry-pick)
-  <kbd>C</kbd>: copy commit range (cherry-pick)
   <kbd>v</kbd>: paste commits (cherry-pick)
   <kbd>ctrl+l</kbd>: open log menu
-  <kbd>g</kbd>: reset to this commit
-  <kbd>space</kbd>: checkout commit
   <kbd>T</kbd>: tag commit
-  <kbd>ctrl+y</kbd>: copy commit message to clipboard
+  <kbd>space</kbd>: checkout commit
+  <kbd>y</kbd>: copy commit attribute
   <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: create new branch off of commit
+  <kbd>g</kbd>: reset to this commit
+  <kbd>c</kbd>: copy commit (cherry-pick)
+  <kbd>C</kbd>: copy commit range (cherry-pick)
   <kbd>enter</kbd>: view selected item's files
 </pre>
 
@@ -165,7 +167,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: copy commit SHA to clipboard
   <kbd>space</kbd>: checkout commit
-  <kbd>g</kbd>: view reset options
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: create new branch off of commit
+  <kbd>g</kbd>: reset to this commit
   <kbd>c</kbd>: copy commit (cherry-pick)
   <kbd>C</kbd>: copy commit range (cherry-pick)
   <kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index c0accbd44e2..8fbf16ccd9b 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -130,8 +130,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
   <kbd>space</kbd>: checkout commit
-  <kbd>g</kbd>: bekijk reset opties
-  <kbd>n</kbd>: nieuwe branch
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: creëer nieuwe branch van commit
+  <kbd>g</kbd>: reset naar deze commit
   <kbd>c</kbd>: kopieer commit (cherry-pick)
   <kbd>C</kbd>: kopieer commit reeks (cherry-pick)
   <kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
@@ -187,16 +189,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   <kbd>ctrl+k</kbd>: verplaats commit 1 naar boven
   <kbd>A</kbd>: wijzig commit met staged veranderingen
   <kbd>t</kbd>: commit ongedaan maken
-  <kbd>n</kbd>: creëer nieuwe branch van commit
-  <kbd>c</kbd>: kopieer commit (cherry-pick)
-  <kbd>C</kbd>: kopieer commit reeks (cherry-pick)
   <kbd>v</kbd>: plak commits (cherry-pick)
   <kbd>ctrl+l</kbd>: open log menu
-  <kbd>g</kbd>: reset naar deze commit
-  <kbd>space</kbd>: checkout commit
   <kbd>T</kbd>: tag commit
-  <kbd>ctrl+y</kbd>: kopieer commit bericht naar klembord
+  <kbd>space</kbd>: checkout commit
+  <kbd>y</kbd>: copy commit attribute
   <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: creëer nieuwe branch van commit
+  <kbd>g</kbd>: reset naar deze commit
+  <kbd>c</kbd>: kopieer commit (cherry-pick)
+  <kbd>C</kbd>: kopieer commit reeks (cherry-pick)
   <kbd>enter</kbd>: bekijk gecommite bestanden
 </pre>
 
@@ -205,7 +207,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
   <kbd>space</kbd>: checkout commit
-  <kbd>g</kbd>: bekijk reset opties
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: creëer nieuwe branch van commit
+  <kbd>g</kbd>: reset naar deze commit
   <kbd>c</kbd>: kopieer commit (cherry-pick)
   <kbd>C</kbd>: kopieer commit reeks (cherry-pick)
   <kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 9c619f6b691..93f91050a62 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -60,16 +60,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   <kbd>ctrl+k</kbd>: przenieś commit 1 w górę
   <kbd>A</kbd>: popraw commit zmianami z poczekalni
   <kbd>t</kbd>: odwróć commit
-  <kbd>n</kbd>: create new branch off of commit
-  <kbd>c</kbd>: kopiuj commit (przebieranie)
-  <kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
   <kbd>v</kbd>: wklej commity (przebieranie)
   <kbd>ctrl+l</kbd>: open log menu
-  <kbd>g</kbd>: zresetuj do tego commita
-  <kbd>space</kbd>: checkout commit
   <kbd>T</kbd>: tag commit
-  <kbd>ctrl+y</kbd>: copy commit message to clipboard
+  <kbd>space</kbd>: checkout commit
+  <kbd>y</kbd>: copy commit attribute
   <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: create new branch off of commit
+  <kbd>g</kbd>: zresetuj do tego commita
+  <kbd>c</kbd>: kopiuj commit (przebieranie)
+  <kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
   <kbd>enter</kbd>: przeglądaj pliki commita
 </pre>
 
@@ -78,7 +78,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: copy commit SHA to clipboard
   <kbd>space</kbd>: checkout commit
-  <kbd>g</kbd>: wyświetl opcje resetu
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: create new branch off of commit
+  <kbd>g</kbd>: zresetuj do tego commita
   <kbd>c</kbd>: kopiuj commit (przebieranie)
   <kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
   <kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
@@ -140,8 +143,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: copy commit SHA to clipboard
   <kbd>space</kbd>: checkout commit
-  <kbd>g</kbd>: wyświetl opcje resetu
-  <kbd>n</kbd>: nowa gałąź
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: open commit in browser
+  <kbd>n</kbd>: create new branch off of commit
+  <kbd>g</kbd>: zresetuj do tego commita
   <kbd>c</kbd>: kopiuj commit (przebieranie)
   <kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
   <kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md
index 408bfe90f86..7e1b1c407c9 100644
--- a/docs/keybindings/Keybindings_zh.md
+++ b/docs/keybindings/Keybindings_zh.md
@@ -126,8 +126,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
   <kbd>space</kbd>: 检出提交
-  <kbd>g</kbd>: 查看重置选项
-  <kbd>n</kbd>: 新分支
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: 在浏览器中打开提交
+  <kbd>n</kbd>: 从提交创建新分支
+  <kbd>g</kbd>: 重置为此提交
   <kbd>c</kbd>: 复制提交（拣选）
   <kbd>C</kbd>: 复制提交范围（拣选）
   <kbd>ctrl+r</kbd>: 重置已拣选（复制）的提交
@@ -173,7 +175,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 <pre>
   <kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
   <kbd>space</kbd>: 检出提交
-  <kbd>g</kbd>: 查看重置选项
+  <kbd>y</kbd>: copy commit attribute
+  <kbd>o</kbd>: 在浏览器中打开提交
+  <kbd>n</kbd>: 从提交创建新分支
+  <kbd>g</kbd>: 重置为此提交
   <kbd>c</kbd>: 复制提交（拣选）
   <kbd>C</kbd>: 复制提交范围（拣选）
   <kbd>ctrl+r</kbd>: 重置已拣选（复制）的提交
@@ -199,16 +204,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   <kbd>ctrl+k</kbd>: 上移提交
   <kbd>A</kbd>: 用已暂存的更改来修补提交
   <kbd>t</kbd>: 还原提交
-  <kbd>n</kbd>: 从提交创建新分支
-  <kbd>c</kbd>: 复制提交（拣选）
-  <kbd>C</kbd>: 复制提交范围（拣选）
   <kbd>v</kbd>: 粘贴提交（拣选）
   <kbd>ctrl+l</kbd>: 打开日志菜单
-  <kbd>g</kbd>: 重置为此提交
-  <kbd>space</kbd>: 检出提交
   <kbd>T</kbd>: 标签提交
-  <kbd>ctrl+y</kbd>: 将提交消息复制到剪贴板
+  <kbd>space</kbd>: 检出提交
+  <kbd>y</kbd>: copy commit attribute
   <kbd>o</kbd>: 在浏览器中打开提交
+  <kbd>n</kbd>: 从提交创建新分支
+  <kbd>g</kbd>: 重置为此提交
+  <kbd>c</kbd>: 复制提交（拣选）
+  <kbd>C</kbd>: 复制提交范围（拣选）
   <kbd>enter</kbd>: 查看提交的文件
 </pre>
 
diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go
index 75256878f84..1e0a442f2e0 100644
--- a/pkg/commands/git_commands/commit.go
+++ b/pkg/commands/git_commands/commit.go
@@ -74,6 +74,12 @@ func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
 	return strings.TrimSpace(message), err
 }
 
+func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
+	cmdStr := "git show --no-color " + commitSha
+	diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
+	return diff, err
+}
+
 func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error) {
 	return self.GetCommitMessagesFirstLine([]string{sha})
 }
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 51f443243b9..988673f1eea 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -229,28 +229,28 @@ type KeybindingBranchesConfig struct {
 }
 
 type KeybindingCommitsConfig struct {
-	SquashDown                   string `yaml:"squashDown"`
-	RenameCommit                 string `yaml:"renameCommit"`
-	RenameCommitWithEditor       string `yaml:"renameCommitWithEditor"`
-	ViewResetOptions             string `yaml:"viewResetOptions"`
-	MarkCommitAsFixup            string `yaml:"markCommitAsFixup"`
-	CreateFixupCommit            string `yaml:"createFixupCommit"`
-	SquashAboveCommits           string `yaml:"squashAboveCommits"`
-	MoveDownCommit               string `yaml:"moveDownCommit"`
-	MoveUpCommit                 string `yaml:"moveUpCommit"`
-	AmendToCommit                string `yaml:"amendToCommit"`
-	PickCommit                   string `yaml:"pickCommit"`
-	RevertCommit                 string `yaml:"revertCommit"`
-	CherryPickCopy               string `yaml:"cherryPickCopy"`
-	CherryPickCopyRange          string `yaml:"cherryPickCopyRange"`
-	PasteCommits                 string `yaml:"pasteCommits"`
-	TagCommit                    string `yaml:"tagCommit"`
-	CheckoutCommit               string `yaml:"checkoutCommit"`
-	ResetCherryPick              string `yaml:"resetCherryPick"`
-	CopyCommitMessageToClipboard string `yaml:"copyCommitMessageToClipboard"`
-	OpenLogMenu                  string `yaml:"openLogMenu"`
-	OpenInBrowser                string `yaml:"openInBrowser"`
-	ViewBisectOptions            string `yaml:"viewBisectOptions"`
+	SquashDown                     string `yaml:"squashDown"`
+	RenameCommit                   string `yaml:"renameCommit"`
+	RenameCommitWithEditor         string `yaml:"renameCommitWithEditor"`
+	ViewResetOptions               string `yaml:"viewResetOptions"`
+	MarkCommitAsFixup              string `yaml:"markCommitAsFixup"`
+	CreateFixupCommit              string `yaml:"createFixupCommit"`
+	SquashAboveCommits             string `yaml:"squashAboveCommits"`
+	MoveDownCommit                 string `yaml:"moveDownCommit"`
+	MoveUpCommit                   string `yaml:"moveUpCommit"`
+	AmendToCommit                  string `yaml:"amendToCommit"`
+	PickCommit                     string `yaml:"pickCommit"`
+	RevertCommit                   string `yaml:"revertCommit"`
+	CherryPickCopy                 string `yaml:"cherryPickCopy"`
+	CherryPickCopyRange            string `yaml:"cherryPickCopyRange"`
+	PasteCommits                   string `yaml:"pasteCommits"`
+	TagCommit                      string `yaml:"tagCommit"`
+	CheckoutCommit                 string `yaml:"checkoutCommit"`
+	ResetCherryPick                string `yaml:"resetCherryPick"`
+	CopyCommitAttributeToClipboard string `yaml:"copyCommitAttributeToClipboard"`
+	OpenLogMenu                    string `yaml:"openLogMenu"`
+	OpenInBrowser                  string `yaml:"openInBrowser"`
+	ViewBisectOptions              string `yaml:"viewBisectOptions"`
 }
 
 type KeybindingStashConfig struct {
@@ -496,28 +496,28 @@ func GetDefaultConfig() *UserConfig {
 				FetchRemote:            "f",
 			},
 			Commits: KeybindingCommitsConfig{
-				SquashDown:                   "s",
-				RenameCommit:                 "r",
-				RenameCommitWithEditor:       "R",
-				ViewResetOptions:             "g",
-				MarkCommitAsFixup:            "f",
-				CreateFixupCommit:            "F",
-				SquashAboveCommits:           "S",
-				MoveDownCommit:               "<c-j>",
-				MoveUpCommit:                 "<c-k>",
-				AmendToCommit:                "A",
-				PickCommit:                   "p",
-				RevertCommit:                 "t",
-				CherryPickCopy:               "c",
-				CherryPickCopyRange:          "C",
-				PasteCommits:                 "v",
-				TagCommit:                    "T",
-				CheckoutCommit:               "<space>",
-				ResetCherryPick:              "<c-R>",
-				CopyCommitMessageToClipboard: "<c-y>",
-				OpenLogMenu:                  "<c-l>",
-				OpenInBrowser:                "o",
-				ViewBisectOptions:            "b",
+				SquashDown:                     "s",
+				RenameCommit:                   "r",
+				RenameCommitWithEditor:         "R",
+				ViewResetOptions:               "g",
+				MarkCommitAsFixup:              "f",
+				CreateFixupCommit:              "F",
+				SquashAboveCommits:             "S",
+				MoveDownCommit:                 "<c-j>",
+				MoveUpCommit:                   "<c-k>",
+				AmendToCommit:                  "A",
+				PickCommit:                     "p",
+				RevertCommit:                   "t",
+				CherryPickCopy:                 "c",
+				CherryPickCopyRange:            "C",
+				PasteCommits:                   "v",
+				TagCommit:                      "T",
+				CheckoutCommit:                 "<space>",
+				ResetCherryPick:                "<c-R>",
+				CopyCommitAttributeToClipboard: "y",
+				OpenLogMenu:                    "<c-l>",
+				OpenInBrowser:                  "o",
+				ViewBisectOptions:              "b",
 			},
 			Stash: KeybindingStashConfig{
 				PopStash: "g",
diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go
index 46e3be2cde5..cc7a2a0d22b 100644
--- a/pkg/gui/context/local_commits_context.go
+++ b/pkg/gui/context/local_commits_context.go
@@ -108,3 +108,7 @@ func (self *LocalCommitsViewModel) SetShowWholeGitGraph(value bool) {
 func (self *LocalCommitsViewModel) GetShowWholeGitGraph() bool {
 	return self.showWholeGitGraph
 }
+
+func (self *LocalCommitsViewModel) GetCommits() []*models.Commit {
+	return self.getModel()
+}
diff --git a/pkg/gui/context/reflog_commits_context.go b/pkg/gui/context/reflog_commits_context.go
index 81580551523..a1ad6cfda16 100644
--- a/pkg/gui/context/reflog_commits_context.go
+++ b/pkg/gui/context/reflog_commits_context.go
@@ -70,3 +70,7 @@ func (self *ReflogCommitsContext) GetSelectedRefName() string {
 
 	return item.RefName()
 }
+
+func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
+	return self.getModel()
+}
diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go
index 93a0c359399..315093f8f44 100644
--- a/pkg/gui/context/sub_commits_context.go
+++ b/pkg/gui/context/sub_commits_context.go
@@ -72,3 +72,7 @@ func (self *SubCommitsContext) GetSelectedRefName() string {
 
 	return item.RefName()
 }
+
+func (self *SubCommitsContext) GetCommits() []*models.Commit {
+	return self.getModel()
+}
diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go
index 3e33783e1c8..8e28e7f6ea8 100644
--- a/pkg/gui/controllers.go
+++ b/pkg/gui/controllers.go
@@ -74,9 +74,6 @@ func (gui *Gui) resetControllers() {
 
 	bisectController := controllers.NewBisectController(common)
 
-	reflogController := controllers.NewReflogController(common)
-	subCommitsController := controllers.NewSubCommitsController(common)
-
 	getSavedCommitMessage := func() string {
 		return gui.State.savedCommitMessage
 	}
@@ -132,31 +129,35 @@ func (gui *Gui) resetControllers() {
 	stashController := controllers.NewStashController(common)
 	commitFilesController := controllers.NewCommitFilesController(common)
 
-	switchToSubCommitsControllerFactory := controllers.NewSubCommitsSwitchControllerFactory(
-		common,
-		func(commits []*models.Commit) { gui.State.Model.SubCommits = commits },
-	)
+	setSubCommits := func(commits []*models.Commit) { gui.State.Model.SubCommits = commits }
 
-	for _, context := range []controllers.ContextWithRefName{
+	for _, context := range []controllers.CanSwitchToSubCommits{
 		gui.State.Contexts.Branches,
 		gui.State.Contexts.RemoteBranches,
 		gui.State.Contexts.Tags,
 	} {
-		controllers.AttachControllers(context, switchToSubCommitsControllerFactory.Create(context))
+		controllers.AttachControllers(context, controllers.NewSwitchToSubCommitsController(
+			common, setSubCommits, context,
+		))
 	}
 
-	commitishControllerFactory := controllers.NewCommitishControllerFactory(
-		common,
-		gui.SwitchToCommitFilesContext,
-	)
-
-	for _, context := range []controllers.Commitish{
+	for _, context := range []controllers.CanSwitchToDiffFiles{
 		gui.State.Contexts.LocalCommits,
 		gui.State.Contexts.ReflogCommits,
 		gui.State.Contexts.SubCommits,
 		gui.State.Contexts.Stash,
 	} {
-		controllers.AttachControllers(context, commitishControllerFactory.Create(context))
+		controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
+			common, gui.SwitchToCommitFilesContext, context,
+		))
+	}
+
+	for _, context := range []controllers.ContainsCommits{
+		gui.State.Contexts.LocalCommits,
+		gui.State.Contexts.ReflogCommits,
+		gui.State.Contexts.SubCommits,
+	} {
+		controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
 	}
 
 	controllers.AttachControllers(gui.State.Contexts.Branches, branchesController, gitFlowController)
@@ -164,8 +165,6 @@ func (gui *Gui) resetControllers() {
 	controllers.AttachControllers(gui.State.Contexts.Tags, gui.Controllers.Tags)
 	controllers.AttachControllers(gui.State.Contexts.Submodules, gui.Controllers.Submodules)
 	controllers.AttachControllers(gui.State.Contexts.LocalCommits, gui.Controllers.LocalCommits, bisectController)
-	controllers.AttachControllers(gui.State.Contexts.ReflogCommits, reflogController)
-	controllers.AttachControllers(gui.State.Contexts.SubCommits, subCommitsController)
 	controllers.AttachControllers(gui.State.Contexts.CommitFiles, commitFilesController)
 	controllers.AttachControllers(gui.State.Contexts.Remotes, gui.Controllers.Remotes)
 	controllers.AttachControllers(gui.State.Contexts.Stash, stashController)
diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go
new file mode 100644
index 00000000000..2065b54c59f
--- /dev/null
+++ b/pkg/gui/controllers/basic_commits_controller.go
@@ -0,0 +1,224 @@
+package controllers
+
+import (
+	"github.com/jesseduffield/gocui"
+	"github.com/jesseduffield/lazygit/pkg/commands/models"
+	"github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+// This controller is for all contexts that contain a list of commits.
+
+var _ types.IController = &BasicCommitsController{}
+
+type ContainsCommits interface {
+	types.Context
+	GetSelected() *models.Commit
+	GetCommits() []*models.Commit
+	GetSelectedLineIdx() int
+}
+
+type BasicCommitsController struct {
+	baseController
+	*controllerCommon
+	context ContainsCommits
+}
+
+func NewBasicCommitsController(controllerCommon *controllerCommon, context ContainsCommits) *BasicCommitsController {
+	return &BasicCommitsController{
+		baseController:   baseController{},
+		controllerCommon: controllerCommon,
+		context:          context,
+	}
+}
+
+func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+	bindings := []*types.Binding{
+		{
+			Key:         opts.GetKey(opts.Config.Commits.CheckoutCommit),
+			Handler:     self.checkSelected(self.checkout),
+			Description: self.c.Tr.LcCheckoutCommit,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard),
+			Handler:     self.checkSelected(self.copyCommitAttribute),
+			Description: self.c.Tr.LcCopyCommitAttributeToClipboard,
+			OpensMenu:   true,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Commits.OpenInBrowser),
+			Handler:     self.checkSelected(self.openInBrowser),
+			Description: self.c.Tr.LcOpenCommitInBrowser,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Universal.New),
+			Modifier:    gocui.ModNone,
+			Handler:     self.checkSelected(self.newBranch),
+			Description: self.c.Tr.LcCreateNewBranchFromCommit,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Commits.ViewResetOptions),
+			Handler:     self.checkSelected(self.createResetMenu),
+			Description: self.c.Tr.LcResetToThisCommit,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopy),
+			Handler:     self.checkSelected(self.copy),
+			Description: self.c.Tr.LcCherryPickCopy,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopyRange),
+			Handler:     self.checkSelected(self.copyRange),
+			Description: self.c.Tr.LcCherryPickCopyRange,
+		},
+		{
+			Key:         opts.GetKey(opts.Config.Commits.ResetCherryPick),
+			Handler:     self.helpers.CherryPick.Reset,
+			Description: self.c.Tr.LcResetCherryPick,
+		},
+	}
+
+	return bindings
+}
+
+func (self *BasicCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
+	return func() error {
+		commit := self.context.GetSelected()
+		if commit == nil {
+			return nil
+		}
+
+		return callback(commit)
+	}
+}
+
+func (self *BasicCommitsController) Context() types.Context {
+	return self.context
+}
+
+func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error {
+	return self.c.Menu(types.CreateMenuOptions{
+		Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard,
+		Items: []*types.MenuItem{
+			{
+				DisplayString: self.c.Tr.LcCommitSha,
+				OnPress: func() error {
+					return self.copyCommitSHAToClipboard(commit)
+				},
+			},
+			{
+				DisplayString: self.c.Tr.LcCommitURL,
+				OnPress: func() error {
+					return self.copyCommitURLToClipboard(commit)
+				},
+			},
+			{
+				DisplayString: self.c.Tr.LcCommitDiff,
+				OnPress: func() error {
+					return self.copyCommitDiffToClipboard(commit)
+				},
+			},
+			{
+				DisplayString: self.c.Tr.LcCommitMessage,
+				OnPress: func() error {
+					return self.copyCommitMessageToClipboard(commit)
+				},
+			},
+		},
+	})
+}
+
+func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Commit) error {
+	self.c.LogAction(self.c.Tr.Actions.CopyCommitSHAToClipboard)
+	if err := self.os.CopyToClipboard(commit.Sha); err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.Toast(self.c.Tr.CommitSHACopiedToClipboard)
+	return nil
+}
+
+func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
+	url, err := self.helpers.Host.GetCommitURL(commit.Sha)
+	if err != nil {
+		return err
+	}
+
+	self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
+	if err := self.os.CopyToClipboard(url); err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.Toast(self.c.Tr.CommitURLCopiedToClipboard)
+	return nil
+}
+
+func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error {
+	diff, err := self.git.Commit.GetCommitDiff(commit.Sha)
+	if err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
+	if err := self.os.CopyToClipboard(diff); err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.Toast(self.c.Tr.CommitDiffCopiedToClipboard)
+	return nil
+}
+
+func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
+	message, err := self.git.Commit.GetCommitMessage(commit.Sha)
+	if err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
+	if err := self.os.CopyToClipboard(message); err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard)
+	return nil
+}
+
+func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
+	url, err := self.helpers.Host.GetCommitURL(commit.Sha)
+	if err != nil {
+		return self.c.Error(err)
+	}
+
+	self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
+	if err := self.os.OpenLink(url); err != nil {
+		return self.c.Error(err)
+	}
+
+	return nil
+}
+
+func (self *BasicCommitsController) newBranch(commit *models.Commit) error {
+	return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
+}
+
+func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
+	return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
+}
+
+func (self *BasicCommitsController) checkout(commit *models.Commit) error {
+	return self.c.Ask(types.AskOpts{
+		Title:  self.c.Tr.LcCheckoutCommit,
+		Prompt: self.c.Tr.SureCheckoutThisCommit,
+		HandleConfirm: func() error {
+			self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
+			return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
+		},
+	})
+}
+
+func (self *BasicCommitsController) copy(commit *models.Commit) error {
+	return self.helpers.CherryPick.Copy(commit, self.context.GetCommits(), self.context)
+}
+
+func (self *BasicCommitsController) copyRange(*models.Commit) error {
+	return self.helpers.CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
+}
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index 45c65e7ee51..41433068dfe 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -3,7 +3,6 @@ package controllers
 import (
 	"fmt"
 
-	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -101,22 +100,6 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
 			Handler:     self.checkSelected(self.revert),
 			Description: self.c.Tr.LcRevertCommit,
 		},
-		{
-			Key:         opts.GetKey(opts.Config.Universal.New),
-			Modifier:    gocui.ModNone,
-			Handler:     self.checkSelected(self.newBranch),
-			Description: self.c.Tr.LcCreateNewBranchFromCommit,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopy),
-			Handler:     self.checkSelected(self.copy),
-			Description: self.c.Tr.LcCherryPickCopy,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopyRange),
-			Handler:     self.checkSelected(self.copyRange),
-			Description: self.c.Tr.LcCherryPickCopyRange,
-		},
 		{
 			Key:         opts.GetKey(opts.Config.Commits.PasteCommits),
 			Handler:     opts.Guards.OutsideFilterMode(self.paste),
@@ -149,31 +132,11 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
 			Description: self.c.Tr.LcOpenLogMenu,
 			OpensMenu:   true,
 		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.ViewResetOptions),
-			Handler:     self.checkSelected(self.createResetMenu),
-			Description: self.c.Tr.LcResetToThisCommit,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CheckoutCommit),
-			Handler:     self.checkSelected(self.checkout),
-			Description: self.c.Tr.LcCheckoutCommit,
-		},
 		{
 			Key:         opts.GetKey(opts.Config.Commits.TagCommit),
 			Handler:     self.checkSelected(self.createTag),
 			Description: self.c.Tr.LcTagCommit,
 		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CopyCommitMessageToClipboard),
-			Handler:     self.checkSelected(self.copyCommitMessageToClipboard),
-			Description: self.c.Tr.LcCopyCommitMessageToClipboard,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.OpenInBrowser),
-			Handler:     self.checkSelected(self.openInBrowser),
-			Description: self.c.Tr.LcOpenCommitInBrowser,
-		},
 	}...)
 
 	return bindings
@@ -556,21 +519,6 @@ func (self *LocalCommitsController) createTag(commit *models.Commit) error {
 	return self.helpers.Tags.CreateTagMenu(commit.Sha, func() {})
 }
 
-func (self *LocalCommitsController) checkout(commit *models.Commit) error {
-	return self.c.Ask(types.AskOpts{
-		Title:  self.c.Tr.LcCheckoutCommit,
-		Prompt: self.c.Tr.SureCheckoutThisCommit,
-		HandleConfirm: func() error {
-			self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
-			return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
-		},
-	})
-}
-
-func (self *LocalCommitsController) createResetMenu(commit *models.Commit) error {
-	return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
-}
-
 func (self *LocalCommitsController) openSearch() error {
 	// we usually lazyload these commits but now that we're searching we need to load them now
 	if self.context().GetLimitCommits() {
@@ -599,22 +547,6 @@ func (self *LocalCommitsController) gotoBottom() error {
 	return nil
 }
 
-func (self *LocalCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
-	message, err := self.git.Commit.GetCommitMessage(commit.Sha)
-	if err != nil {
-		return self.c.Error(err)
-	}
-
-	self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
-	if err := self.os.CopyToClipboard(message); err != nil {
-		return self.c.Error(err)
-	}
-
-	self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard)
-
-	return nil
-}
-
 func (self *LocalCommitsController) handleOpenLogMenu() error {
 	return self.c.Menu(types.CreateMenuOptions{
 		Title: self.c.Tr.LogMenuTitle,
@@ -698,20 +630,6 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
 	})
 }
 
-func (self *LocalCommitsController) openInBrowser(commit *models.Commit) error {
-	url, err := self.helpers.Host.GetCommitURL(commit.Sha)
-	if err != nil {
-		return self.c.Error(err)
-	}
-
-	self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
-	if err := self.os.OpenLink(url); err != nil {
-		return self.c.Error(err)
-	}
-
-	return nil
-}
-
 func (self *LocalCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
 	return func() error {
 		commit := self.context().GetSelected()
@@ -731,18 +649,6 @@ func (self *LocalCommitsController) context() *context.LocalCommitsContext {
 	return self.contexts.LocalCommits
 }
 
-func (self *LocalCommitsController) newBranch(commit *models.Commit) error {
-	return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
-}
-
-func (self *LocalCommitsController) copy(commit *models.Commit) error {
-	return self.helpers.CherryPick.Copy(commit, self.model.Commits, self.context())
-}
-
-func (self *LocalCommitsController) copyRange(*models.Commit) error {
-	return self.helpers.CherryPick.CopyRange(self.context().GetSelectedLineIdx(), self.model.Commits, self.context())
-}
-
 func (self *LocalCommitsController) paste() error {
 	return self.helpers.CherryPick.Paste()
 }
diff --git a/pkg/gui/controllers/reflog_controller.go b/pkg/gui/controllers/reflog_controller.go
deleted file mode 100644
index 4085df63589..00000000000
--- a/pkg/gui/controllers/reflog_controller.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package controllers
-
-import (
-	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/gui/context"
-	"github.com/jesseduffield/lazygit/pkg/gui/types"
-)
-
-type ReflogController struct {
-	baseController
-	*controllerCommon
-}
-
-var _ types.IController = &ReflogController{}
-
-func NewReflogController(
-	common *controllerCommon,
-) *ReflogController {
-	return &ReflogController{
-		baseController:   baseController{},
-		controllerCommon: common,
-	}
-}
-
-func (self *ReflogController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
-	bindings := []*types.Binding{
-		{
-			Key:         opts.GetKey(opts.Config.Universal.Select),
-			Handler:     self.checkSelected(self.checkout),
-			Description: self.c.Tr.LcCheckoutCommit,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.ViewResetOptions),
-			Handler:     self.checkSelected(self.openResetMenu),
-			Description: self.c.Tr.LcViewResetOptions,
-			OpensMenu:   true,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopy),
-			Handler:     opts.Guards.OutsideFilterMode(self.checkSelected(self.copy)),
-			Description: self.c.Tr.LcCherryPickCopy,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopyRange),
-			Handler:     opts.Guards.OutsideFilterMode(self.checkSelected(self.copyRange)),
-			Description: self.c.Tr.LcCherryPickCopyRange,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.ResetCherryPick),
-			Handler:     self.helpers.CherryPick.Reset,
-			Description: self.c.Tr.LcResetCherryPick,
-		},
-	}
-
-	return bindings
-}
-
-func (self *ReflogController) checkSelected(callback func(*models.Commit) error) func() error {
-	return func() error {
-		commit := self.context().GetSelected()
-		if commit == nil {
-			return nil
-		}
-
-		return callback(commit)
-	}
-}
-
-func (self *ReflogController) Context() types.Context {
-	return self.context()
-}
-
-func (self *ReflogController) context() *context.ReflogCommitsContext {
-	return self.contexts.ReflogCommits
-}
-
-func (self *ReflogController) checkout(commit *models.Commit) error {
-	err := self.c.Ask(types.AskOpts{
-		Title:  self.c.Tr.LcCheckoutCommit,
-		Prompt: self.c.Tr.SureCheckoutThisCommit,
-		HandleConfirm: func() error {
-			self.c.LogAction(self.c.Tr.Actions.CheckoutReflogCommit)
-			return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
-		},
-	})
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (self *ReflogController) openResetMenu(commit *models.Commit) error {
-	return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
-}
-
-func (self *ReflogController) copy(commit *models.Commit) error {
-	return self.helpers.CherryPick.Copy(commit, self.model.FilteredReflogCommits, self.context())
-}
-
-func (self *ReflogController) copyRange(commit *models.Commit) error {
-	return self.helpers.CherryPick.CopyRange(self.context().GetSelectedLineIdx(), self.model.FilteredReflogCommits, self.context())
-}
diff --git a/pkg/gui/controllers/sub_commits_controller.go b/pkg/gui/controllers/sub_commits_controller.go
deleted file mode 100644
index 36d8b23155e..00000000000
--- a/pkg/gui/controllers/sub_commits_controller.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package controllers
-
-import (
-	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/gui/context"
-	"github.com/jesseduffield/lazygit/pkg/gui/types"
-)
-
-type SubCommitsController struct {
-	baseController
-	*controllerCommon
-}
-
-var _ types.IController = &SubCommitsController{}
-
-func NewSubCommitsController(
-	common *controllerCommon,
-) *SubCommitsController {
-	return &SubCommitsController{
-		baseController:   baseController{},
-		controllerCommon: common,
-	}
-}
-
-func (self *SubCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
-	bindings := []*types.Binding{
-		{
-			Key:         opts.GetKey(opts.Config.Universal.Select),
-			Handler:     self.checkSelected(self.checkout),
-			Description: self.c.Tr.LcCheckoutCommit,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.ViewResetOptions),
-			Handler:     self.checkSelected(self.openResetMenu),
-			Description: self.c.Tr.LcViewResetOptions,
-			OpensMenu:   true,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Universal.New),
-			Handler:     self.checkSelected(self.newBranch),
-			Description: self.c.Tr.LcNewBranch,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopy),
-			Handler:     self.checkSelected(self.copy),
-			Description: self.c.Tr.LcCherryPickCopy,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.CherryPickCopyRange),
-			Handler:     self.checkSelected(self.copyRange),
-			Description: self.c.Tr.LcCherryPickCopyRange,
-		},
-		{
-			Key:         opts.GetKey(opts.Config.Commits.ResetCherryPick),
-			Handler:     self.helpers.CherryPick.Reset,
-			Description: self.c.Tr.LcResetCherryPick,
-		},
-	}
-
-	return bindings
-}
-
-func (self *SubCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
-	return func() error {
-		commit := self.context().GetSelected()
-		if commit == nil {
-			return nil
-		}
-
-		return callback(commit)
-	}
-}
-
-func (self *SubCommitsController) Context() types.Context {
-	return self.context()
-}
-
-func (self *SubCommitsController) context() *context.SubCommitsContext {
-	return self.contexts.SubCommits
-}
-
-func (self *SubCommitsController) checkout(commit *models.Commit) error {
-	err := self.c.Ask(types.AskOpts{
-		Title:  self.c.Tr.LcCheckoutCommit,
-		Prompt: self.c.Tr.SureCheckoutThisCommit,
-		HandleConfirm: func() error {
-			self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
-			return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
-		},
-	})
-	if err != nil {
-		return err
-	}
-
-	self.context().SetSelectedLineIdx(0)
-
-	return nil
-}
-
-func (self *SubCommitsController) openResetMenu(commit *models.Commit) error {
-	return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
-}
-
-func (self *SubCommitsController) newBranch(commit *models.Commit) error {
-	return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
-}
-
-func (self *SubCommitsController) copy(commit *models.Commit) error {
-	return self.helpers.CherryPick.Copy(commit, self.model.SubCommits, self.context())
-}
-
-func (self *SubCommitsController) copyRange(commit *models.Commit) error {
-	return self.helpers.CherryPick.CopyRange(self.context().GetSelectedLineIdx(), self.model.SubCommits, self.context())
-}
diff --git a/pkg/gui/controllers/commitish_controller.go b/pkg/gui/controllers/switch_to_diff_files_controller.go
similarity index 50%
rename from pkg/gui/controllers/commitish_controller.go
rename to pkg/gui/controllers/switch_to_diff_files_controller.go
index 04e271253b6..9a3111caea6 100644
--- a/pkg/gui/controllers/commitish_controller.go
+++ b/pkg/gui/controllers/switch_to_diff_files_controller.go
@@ -6,47 +6,35 @@ import (
 
 // This controller is for all contexts that contain commit files.
 
-type CommitishControllerFactory struct {
-	controllerCommon *controllerCommon
-	viewFiles        func(SwitchToCommitFilesContextOpts) error
-}
-
-var _ types.IController = &CommitishController{}
+var _ types.IController = &SwitchToDiffFilesController{}
 
-type Commitish interface {
+type CanSwitchToDiffFiles interface {
 	types.Context
 	CanRebase() bool
 	GetSelectedRefName() string
 }
 
-type CommitishController struct {
+type SwitchToDiffFilesController struct {
 	baseController
 	*controllerCommon
-	context Commitish
-
+	context   CanSwitchToDiffFiles
 	viewFiles func(SwitchToCommitFilesContextOpts) error
 }
 
-func NewCommitishControllerFactory(
-	common *controllerCommon,
+func NewSwitchToDiffFilesController(
+	controllerCommon *controllerCommon,
 	viewFiles func(SwitchToCommitFilesContextOpts) error,
-) *CommitishControllerFactory {
-	return &CommitishControllerFactory{
-		controllerCommon: common,
-		viewFiles:        viewFiles,
-	}
-}
-
-func (self *CommitishControllerFactory) Create(context Commitish) *CommitishController {
-	return &CommitishController{
+	context CanSwitchToDiffFiles,
+) *SwitchToDiffFilesController {
+	return &SwitchToDiffFilesController{
 		baseController:   baseController{},
-		controllerCommon: self.controllerCommon,
+		controllerCommon: controllerCommon,
 		context:          context,
-		viewFiles:        self.viewFiles,
+		viewFiles:        viewFiles,
 	}
 }
 
-func (self *CommitishController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+func (self *SwitchToDiffFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
 	bindings := []*types.Binding{
 		{
 			Key:         opts.GetKey(opts.Config.Universal.GoInto),
@@ -58,11 +46,11 @@ func (self *CommitishController) GetKeybindings(opts types.KeybindingsOpts) []*t
 	return bindings
 }
 
-func (self *CommitishController) GetOnClick() func() error {
+func (self *SwitchToDiffFilesController) GetOnClick() func() error {
 	return self.checkSelected(self.enter)
 }
 
-func (self *CommitishController) checkSelected(callback func(string) error) func() error {
+func (self *SwitchToDiffFilesController) checkSelected(callback func(string) error) func() error {
 	return func() error {
 		refName := self.context.GetSelectedRefName()
 		if refName == "" {
@@ -73,7 +61,7 @@ func (self *CommitishController) checkSelected(callback func(string) error) func
 	}
 }
 
-func (self *CommitishController) enter(refName string) error {
+func (self *SwitchToDiffFilesController) enter(refName string) error {
 	return self.viewFiles(SwitchToCommitFilesContextOpts{
 		RefName:   refName,
 		CanRebase: self.context.CanRebase(),
@@ -81,6 +69,6 @@ func (self *CommitishController) enter(refName string) error {
 	})
 }
 
-func (self *CommitishController) Context() types.Context {
+func (self *SwitchToDiffFilesController) Context() types.Context {
 	return self.context
 }
diff --git a/pkg/gui/controllers/sub_commits_switch_controller.go b/pkg/gui/controllers/switch_to_sub_commits_controller.go
similarity index 56%
rename from pkg/gui/controllers/sub_commits_switch_controller.go
rename to pkg/gui/controllers/switch_to_sub_commits_controller.go
index 4c8f086a527..abd8642d382 100644
--- a/pkg/gui/controllers/sub_commits_switch_controller.go
+++ b/pkg/gui/controllers/switch_to_sub_commits_controller.go
@@ -6,46 +6,35 @@ import (
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 )
 
-type SubCommitsSwitchControllerFactory struct {
-	controllerCommon *controllerCommon
-	setSubCommits    func([]*models.Commit)
-}
-
-var _ types.IController = &SubCommitsSwitchController{}
+var _ types.IController = &SwitchToSubCommitsController{}
 
-type ContextWithRefName interface {
+type CanSwitchToSubCommits interface {
 	types.Context
 	GetSelectedRefName() string
 }
 
-type SubCommitsSwitchController struct {
+type SwitchToSubCommitsController struct {
 	baseController
 	*controllerCommon
-	context ContextWithRefName
+	context CanSwitchToSubCommits
 
 	setSubCommits func([]*models.Commit)
 }
 
-func NewSubCommitsSwitchControllerFactory(
-	common *controllerCommon,
+func NewSwitchToSubCommitsController(
+	controllerCommon *controllerCommon,
 	setSubCommits func([]*models.Commit),
-) *SubCommitsSwitchControllerFactory {
-	return &SubCommitsSwitchControllerFactory{
-		controllerCommon: common,
-		setSubCommits:    setSubCommits,
-	}
-}
-
-func (self *SubCommitsSwitchControllerFactory) Create(context ContextWithRefName) *SubCommitsSwitchController {
-	return &SubCommitsSwitchController{
+	context CanSwitchToSubCommits,
+) *SwitchToSubCommitsController {
+	return &SwitchToSubCommitsController{
 		baseController:   baseController{},
-		controllerCommon: self.controllerCommon,
+		controllerCommon: controllerCommon,
 		context:          context,
-		setSubCommits:    self.setSubCommits,
+		setSubCommits:    setSubCommits,
 	}
 }
 
-func (self *SubCommitsSwitchController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+func (self *SwitchToSubCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
 	bindings := []*types.Binding{
 		{
 			Handler:     self.viewCommits,
@@ -57,11 +46,11 @@ func (self *SubCommitsSwitchController) GetKeybindings(opts types.KeybindingsOpt
 	return bindings
 }
 
-func (self *SubCommitsSwitchController) GetOnClick() func() error {
+func (self *SwitchToSubCommitsController) GetOnClick() func() error {
 	return self.viewCommits
 }
 
-func (self *SubCommitsSwitchController) viewCommits() error {
+func (self *SwitchToSubCommitsController) viewCommits() error {
 	refName := self.context.GetSelectedRefName()
 	if refName == "" {
 		return nil
@@ -87,6 +76,6 @@ func (self *SubCommitsSwitchController) viewCommits() error {
 	return self.c.PushContext(self.contexts.SubCommits)
 }
 
-func (self *SubCommitsSwitchController) Context() types.Context {
+func (self *SwitchToSubCommitsController) Context() types.Context {
 	return self.context
 }
diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go
index 3f83f67561f..400a12da620 100644
--- a/pkg/i18n/chinese.go
+++ b/pkg/i18n/chinese.go
@@ -474,7 +474,6 @@ func chineseTranslationSet() TranslationSet {
 		Actions: Actions{
 			// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
 			CheckoutCommit:                    "检出提交",
-			CheckoutReflogCommit:              "检出 reflog 提交",
 			CheckoutTag:                       "检出标签",
 			CheckoutBranch:                    "检出分支",
 			ForceCheckoutBranch:               "强制检出分支",
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 4724abcab85..b09b6550c75 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -362,8 +362,13 @@ type TranslationSet struct {
 	LcOpenDiffingMenu                   string
 	LcOpenExtrasMenu                    string
 	LcShowingGitDiff                    string
+	LcCommitDiff                        string
 	LcCopyCommitShaToClipboard          string
+	LcCommitSha                         string
+	LcCommitURL                         string
 	LcCopyCommitMessageToClipboard      string
+	LcCommitMessage                     string
+	LcCopyCommitAttributeToClipboard    string
 	LcCopyBranchNameToClipboard         string
 	LcCopyFileNameToClipboard           string
 	LcCopyCommitFileNameToClipboard     string
@@ -415,6 +420,9 @@ type TranslationSet struct {
 	ExtrasTitle                         string
 	PushingTagStatus                    string
 	PullRequestURLCopiedToClipboard     string
+	CommitDiffCopiedToClipboard         string
+	CommitSHACopiedToClipboard          string
+	CommitURLCopiedToClipboard          string
 	CommitMessageCopiedToClipboard      string
 	LcCopiedToClipboard                 string
 	ErrCannotEditDirectory              string
@@ -480,7 +488,6 @@ type Bisect struct {
 
 type Actions struct {
 	CheckoutCommit                    string
-	CheckoutReflogCommit              string
 	CheckoutTag                       string
 	CheckoutBranch                    string
 	ForceCheckoutBranch               string
@@ -505,6 +512,10 @@ type Actions struct {
 	MoveCommitUp                      string
 	MoveCommitDown                    string
 	CopyCommitMessageToClipboard      string
+	CopyCommitDiffToClipboard         string
+	CopyCommitSHAToClipboard          string
+	CopyCommitURLToClipboard          string
+	CopyCommitAttributeToClipboard    string
 	CustomCommand                     string
 	DiscardAllChangesInDirectory      string
 	DiscardUnstagedChangesInDirectory string
@@ -941,8 +952,13 @@ func EnglishTranslationSet() TranslationSet {
 		// the actual view is the extras view which I intend to give more tabs in future but for now we'll only mention the command log part
 		LcOpenExtrasMenu:                    "open command log menu",
 		LcShowingGitDiff:                    "showing output for:",
+		LcCommitDiff:                        "commit diff",
 		LcCopyCommitShaToClipboard:          "copy commit SHA to clipboard",
+		LcCommitSha:                         "commit SHA",
+		LcCommitURL:                         "commit URL",
 		LcCopyCommitMessageToClipboard:      "copy commit message to clipboard",
+		LcCommitMessage:                     "commit message",
+		LcCopyCommitAttributeToClipboard:    "copy commit attribute",
 		LcCopyBranchNameToClipboard:         "copy branch name to clipboard",
 		LcCopyFileNameToClipboard:           "copy the file name to the clipboard",
 		LcCopyCommitFileNameToClipboard:     "copy the committed file name to the clipboard",
@@ -993,6 +1009,9 @@ func EnglishTranslationSet() TranslationSet {
 		ExtrasTitle:                         "Extras",
 		PushingTagStatus:                    "pushing tag",
 		PullRequestURLCopiedToClipboard:     "Pull request URL copied to clipboard",
+		CommitDiffCopiedToClipboard:         "Commit diff copied to clipboard",
+		CommitSHACopiedToClipboard:          "Commit SHA copied to clipboard",
+		CommitURLCopiedToClipboard:          "Commit URL copied to clipboard",
 		CommitMessageCopiedToClipboard:      "Commit message copied to clipboard",
 		LcCopiedToClipboard:                 "copied to clipboard",
 		ErrCannotEditDirectory:              "Cannot edit directory: you can only edit individual files",
@@ -1040,7 +1059,6 @@ func EnglishTranslationSet() TranslationSet {
 		Actions: Actions{
 			// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
 			CheckoutCommit:                    "Checkout commit",
-			CheckoutReflogCommit:              "Checkout reflog commit",
 			CheckoutTag:                       "Checkout tag",
 			CheckoutBranch:                    "Checkout branch",
 			ForceCheckoutBranch:               "Force checkout branch",
@@ -1064,6 +1082,10 @@ func EnglishTranslationSet() TranslationSet {
 			CreateLightweightTag:              "Create lightweight tag",
 			CreateAnnotatedTag:                "Create annotated tag",
 			CopyCommitMessageToClipboard:      "Copy commit message to clipboard",
+			CopyCommitDiffToClipboard:         "Copy commit diff to clipboard",
+			CopyCommitSHAToClipboard:          "Copy commit SHA to clipboard",
+			CopyCommitURLToClipboard:          "Copy commit URL to clipboard",
+			CopyCommitAttributeToClipboard:    "Copy to clipboard",
 			MoveCommitUp:                      "Move commit up",
 			MoveCommitDown:                    "Move commit down",
 			CustomCommand:                     "Custom command",


Test patch: