diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000000..8143bb75f7c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+*.go text
+*.md text eol=lf
+*.json text eol=lf
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 9d789cd78e1..cb517784fc9 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -6,7 +6,7 @@
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
-* [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
+* [ ] Docs have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc
+
## Elevator Pitch
@@ -371,6 +371,16 @@ nix run nixpkgs#lazygit
Or you can add lazygit to you configuration.nix in the environment.systemPackages section.
More details can be found via NixOs search [page](https://search.nixos.org/).
+### Flox
+
+Lazygit can be installed into a Flox environment as follows.
+
+```sh
+flox install lazygit
+```
+
+More details about Flox can be found on [their website](https://flox.dev/).
+
### FreeBSD
```sh
diff --git a/cmd/i18n/main.go b/cmd/i18n/main.go
new file mode 100644
index 00000000000..f388c396b13
--- /dev/null
+++ b/cmd/i18n/main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "encoding/json"
+ "log"
+ "os"
+
+ "github.com/jesseduffield/lazygit/pkg/i18n"
+)
+
+func saveLanguageFileToJson(tr *i18n.TranslationSet, filepath string) error {
+ jsonData, err := json.MarshalIndent(tr, "", " ")
+ if err != nil {
+ return err
+ }
+
+ jsonData = append(jsonData, '\n')
+ return os.WriteFile(filepath, jsonData, 0o644)
+}
+
+func main() {
+ err := saveLanguageFileToJson(i18n.EnglishTranslationSet(), "en.json")
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/docs/Config.md b/docs/Config.md
index d85637429c3..be579ae8bda 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -29,268 +29,587 @@ to the top of your config file or via [Visual Studio Code settings.json config][
## Default
+
```yaml
+# Config relating to the Lazygit UI
gui:
- # stuff relating to the UI
- windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
- scrollHeight: 2 # how many lines you scroll by
- scrollPastBottom: true # enable scrolling past the bottom
- scrollOffMargin: 2 # how many lines to keep before/after the cursor when it reaches the top/bottom of the view; see 'Scroll-off Margin' section below
- scrollOffBehavior: 'margin' # one of 'margin' | 'jump'; see 'Scroll-off Margin' section below
- sidePanelWidth: 0.3333 # number from 0 to 1
+ # The number of lines you scroll by when scrolling the main window
+ scrollHeight: 2
+
+ # If true, allow scrolling past the bottom of the content in the main window
+ scrollPastBottom: true
+
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin
+ scrollOffMargin: 2
+
+ # One of: 'margin' (default) | 'jump'
+ scrollOffBehavior: margin
+
+ # If true, capture mouse events.
+ # When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
+ mouseEvents: true
+
+ # If true, do not show a warning when discarding changes in the staging view.
+ skipDiscardChangeWarning: false
+
+ # If true, do not show warning when applying/popping the stash
+ skipStashWarning: false
+
+ # If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
+ skipNoStagedFilesWarning: false
+
+ # If true, do not show a warning when rewording a commit via an external editor
+ skipRewordInEditorWarning: false
+
+ # Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
+ # Number from 0 to 1.0.
+ sidePanelWidth: 0.3333
+
+ # If true, increase the height of the focused side window; creating an accordion effect.
expandFocusedSidePanel: false
- mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
- enlargedSideViewLocation: 'left' # one of 'left' | 'top'
- language: 'auto' # one of 'auto' | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
- timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format
- shortTimeFormat: '3:04PM'
+
+ # The weight of the expanded side panel, relative to the other panels. 2 means
+ # twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true.
+ expandedSidePanelWeight: 2
+
+ # Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
+ # Options are:
+ # - 'horizontal': split the window horizontally
+ # - 'vertical': split the window vertically
+ # - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
+ mainPanelSplitMode: flexible
+
+ # How the window is split when in half screen mode (i.e. after hitting '+' once).
+ # Possible values:
+ # - 'left': split the window horizontally (side panel on the left, main view on the right)
+ # - 'top': split the window vertically (side panel on top, main view below)
+ enlargedSideViewLocation: left
+
+ # One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
+ language: auto
+
+ # Format used when displaying time e.g. commit time.
+ # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
+ timeFormat: 02 Jan 06
+
+ # Format used when displaying time if the time is less than 24 hours ago.
+ # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
+ shortTimeFormat: 3:04PM
+
+ # Config relating to colors and styles.
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes
theme:
+ # Border color of focused window
activeBorderColor:
- green
- bold
+
+ # Border color of non-focused windows
inactiveBorderColor:
- - white
+ - default
+
+ # Border color of focused window when searching in that window
searchingActiveBorderColor:
- cyan
- bold
+
+ # Color of keybindings help text in the bottom line
optionsTextColor:
- blue
+
+ # Background color of selected line.
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
selectedLineBgColor:
- - blue # set to `default` to have no background colour
+ - blue
+
+ # Background color of selected line when view doesn't have focus.
+ inactiveViewSelectedLineBgColor:
+ - bold
+
+ # Foreground color of copied commit
+ cherryPickedCommitFgColor:
+ - blue
+
+ # Background color of copied commit
cherryPickedCommitBgColor:
- cyan
- cherryPickedCommitFgColor:
+
+ # Foreground color of marked base commit (for rebase)
+ markedBaseCommitFgColor:
- blue
+
+ # Background color of marked base commit (for rebase)
+ markedBaseCommitBgColor:
+ - yellow
+
+ # Color for file with unstaged changes
unstagedChangesColor:
- red
+
+ # Default text color
defaultFgColor:
- default
+
+ # Config relating to the commit length indicator
commitLength:
+ # If true, show an indicator of commit message length
show: true
- mouseEvents: true
- skipDiscardChangeWarning: false
- skipStashWarning: false
- showFileTree: true # for rendering changes files in a tree format
- showListFooter: true # for seeing the '5 of 20' message in list panels
+
+ # If true, show the '5 of 20' footer at the bottom of list views
+ showListFooter: true
+
+ # If true, display the files in the file views as a tree. If false, display the files as a flat list.
+ # This can be toggled from within Lazygit with the '~' key, but that will not change the default.
+ showFileTree: true
+
+ # If true, show a random tip in the command log when Lazygit starts
showRandomTip: true
- showBranchCommitHash: false # show commit hashes alongside branch names
- showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
- showPanelJumps: true # for showing the jump-to-panel keybindings as panel subtitles
+
+ # If true, show the command log
showCommandLog: true
- showIcons: false # deprecated: use nerdFontsVersion instead
- nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons
- showFileIcons: true # for hiding file icons in the file views
- commitHashLength: 8 # length of commit hash in commits view. 0 shows '*' if NF icons aren't enabled
+
+ # If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.
+ showBottomLine: true
+
+ # If true, show jump-to-window keybindings in window titles.
+ showPanelJumps: true
+
+ # Deprecated: use nerdFontsVersion instead
+ showIcons: false
+
+ # Nerd fonts version to use.
+ # One of: '2' | '3' | empty string (default)
+ # If empty, do not show icons.
+ nerdFontsVersion: ""
+
+ # If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.
+ showFileIcons: true
+
+ # Length of author name in (non-expanded) commits view. 2 means show initials only.
+ commitAuthorShortLength: 2
+
+ # Length of author name in expanded commits view. 2 means show initials only.
+ commitAuthorLongLength: 17
+
+ # Length of commit hash in commits view. 0 shows '*' if NF icons aren't on.
+ commitHashLength: 8
+
+ # If true, show commit hashes alongside branch names in the branches view.
+ showBranchCommitHash: false
+
+ # Whether to show the divergence from the base branch in the branches view.
+ # One of: 'none' | 'onlyArrow' | 'arrowAndNumber'
+ showDivergenceFromBaseBranch: none
+
+ # Height of the command log view
commandLogSize: 8
- splitDiff: 'auto' # one of 'auto' | 'always'
- skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
- border: 'rounded' # one of 'single' | 'double' | 'rounded' | 'hidden'
- animateExplosion: true # shows an explosion animation when nuking the working tree
- portraitMode: 'auto' # one of 'auto' | 'never' | 'always'
- filterMode: 'substring' # one of 'substring' | 'fuzzy'; see 'Filtering' section below
+
+ # Whether to split the main window when viewing file changes.
+ # One of: 'auto' | 'always'
+ # If 'auto', only split the main window when a file has both staged and unstaged changes
+ splitDiff: auto
+
+ # Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
+ # One of: 'normal' (default) | 'half' | 'full'
+ windowSize: normal
+
+ # Window border style.
+ # One of 'rounded' (default) | 'single' | 'double' | 'hidden'
+ border: rounded
+
+ # If true, show a seriously epic explosion animation when nuking the working tree.
+ animateExplosion: true
+
+ # Whether to stack UI components on top of each other.
+ # One of 'auto' (default) | 'always' | 'never'
+ portraitMode: auto
+
+ # How things are filtered when typing '/'.
+ # One of 'substring' (default) | 'fuzzy'
+ filterMode: substring
+
+ # Config relating to the spinner.
spinner:
- frames: ['|', '/', '-', '\\']
- rate: 50 # spinner rate in milliseconds
- statusPanelView: 'dashboard' # one of 'dashboard' | 'allBranchesLog'
+ # The frames of the spinner animation.
+ frames:
+ - '|'
+ - /
+ - '-'
+ - \
+
+ # The "speed" of the spinner in milliseconds.
+ rate: 50
+
+ # Status panel view.
+ # One of 'dashboard' (default) | 'allBranchesLog'
+ statusPanelView: dashboard
+
+# Config relating to git
git:
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
paging:
+ # Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
colorArg: always
+
+ # e.g.
+ # diff-so-fancy
+ # delta --dark --paging=never
+ # ydiff -p cat -s --wrap --width={{columnWidth}}
+ pager: ""
+
+ # If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
useConfig: false
+
+ # e.g. 'difft --color=always'
+ externalDiffCommand: ""
+
+ # Config relating to committing
commit:
+ # If true, pass '--signoff' flag when committing
signOff: false
- autoWrapCommitMessage: true # automatic WYSIWYG wrapping of the commit message as you type
- autoWrapWidth: 72 # if autoWrapCommitMessage is true, the width to wrap to
+
+ # Automatic WYSIWYG wrapping of the commit message as you type
+ autoWrapCommitMessage: true
+
+ # If autoWrapCommitMessage is true, the width to wrap to
+ autoWrapWidth: 72
+
+ # Config relating to merging
merging:
- # only applicable to unix users
+ # If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
+ # Only applicable to unix users.
manualCommit: false
- # extra args passed to `git merge`, e.g. --no-ff
- args: ''
+
+ # Extra args passed to `git merge`, e.g. --no-ff
+ args: ""
+
+ # The commit message to use for a squash merge commit. Can contain "{{selectedRef}}" and "{{currentBranch}}" placeholders.
+ squashMergeMessage: Squash merge {{selectedRef}} into {{currentBranch}}
+
+ # list of branches that are considered 'main' branches, used when displaying commits
+ mainBranches:
+ - master
+ - main
+
+ # Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
+ skipHookPrefix: WIP
+
+ # If true, periodically fetch from remote
+ autoFetch: true
+
+ # If true, periodically refresh files and submodules
+ autoRefresh: true
+
+ # If true, pass the --all arg to git fetch
+ fetchAll: true
+
+ # Command used when displaying the current branch git log in the main window
+ branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
+
+ # Command used to display git log of all branches in the main window.
+ # Deprecated: User `allBranchesLogCmds` instead.
+ allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
+
+ # If true, do not spawn a separate process when using GPG
+ overrideGpg: false
+
+ # If true, do not allow force pushes
+ disableForcePushing: false
+
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
+ commitPrefix:
+ # pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
+ pattern: ""
+
+ # Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
+ replace: ""
+
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix
+ branchPrefix: ""
+
+ # If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
+ # (This should really be under 'gui', not 'git')
+ parseEmoji: false
+
+ # Config for showing the log in the commits view
log:
- # one of date-order, author-date-order, topo-order or default.
- # topo-order makes it easier to read the git log graph, but commits may not
- # appear chronologically. See https://git-scm.com/docs/git-log#_commit_ordering
+ # One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default'
+ # 'topo-order' makes it easier to read the git log graph, but commits may not
+ # appear chronologically. See https://git-scm.com/docs/
#
# Deprecated: Configure this with `Log menu -> Commit sort order` ( in the commits window by default).
- order: 'topo-order'
- # one of always, never, when-maximised
- # this determines whether the git graph is rendered in the commits panel
+ order: topo-order
+
+ # This determines whether the git graph is rendered in the commits panel
+ # One of 'always' | 'never' | 'when-maximised'
#
# Deprecated: Configure this with `Log menu -> Show git graph` ( in the commits window by default).
- showGraph: 'always'
- # displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
+ showGraph: always
+
+ # displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
showWholeGraph: false
- skipHookPrefix: WIP
- # The main branches. We colour commits green if they belong to one of these branches,
- # so that you can easily see which commits are unique to your branch (coloured in yellow)
- mainBranches: [master, main]
- autoFetch: true
- autoRefresh: true
- fetchAll: true # Pass --all flag when running git fetch. Set to false to fetch only origin (or the current branch's upstream remote if there is one)
- branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
- allBranchesLogCmd: 'git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium'
- overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
- disableForcePushing: false
- parseEmoji: false
- truncateCopiedCommitHashesTo: 12 # When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation.
-os:
- copyToClipboardCmd: '' # See 'Custom Command for Copying to Clipboard' section
- editPreset: '' # see 'Configuring File Editing' section
- edit: ''
- editAtLine: ''
- editAtLineAndWait: ''
- open: ''
- openLink: ''
-refresher:
- refreshInterval: 10 # File/submodule refresh interval in seconds. Auto-refresh can be disabled via option 'git.autoRefresh'.
- fetchInterval: 60 # Re-fetch interval in seconds. Auto-fetch can be disabled via option 'git.autoFetch'.
+
+ # When copying commit hashes to the clipboard, truncate them to this
+ # length. Set to 40 to disable truncation.
+ truncateCopiedCommitHashesTo: 12
+
+# Periodic update checks
update:
- method: prompt # can be: prompt | background | never
- days: 14 # how often an update is checked for
+ # One of: 'prompt' (default) | 'background' | 'never'
+ method: prompt
+
+ # Period in days between update checks
+ days: 14
+
+# Background refreshes
+refresher:
+ # File/submodule refresh interval in seconds.
+ # Auto-refresh can be disabled via option 'git.autoRefresh'.
+ refreshInterval: 10
+
+ # Re-fetch interval in seconds.
+ # Auto-fetch can be disabled via option 'git.autoFetch'.
+ fetchInterval: 60
+
+# If true, show a confirmation popup before quitting Lazygit
confirmOnQuit: false
-# determines whether hitting 'esc' will quit the application when there is nothing to cancel/close
+
+# If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
quitOnTopLevelReturn: false
+
+# Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
+os:
+ # Command for editing a file. Should contain "{{filename}}".
+ edit: ""
+
+ # Command for editing a file at a given line number. Should contain
+ # "{{filename}}", and may optionally contain "{{line}}".
+ editAtLine: ""
+
+ # Same as EditAtLine, except that the command needs to wait until the
+ # window is closed.
+ editAtLineAndWait: ""
+
+ # For opening a directory in an editor
+ openDirInEditor: ""
+
+ # A built-in preset that sets all of the above settings. Supported presets
+ # are defined in the getPreset function in editor_presets.go.
+ editPreset: ""
+
+ # Command for opening a file, as if the file is double-clicked. Should
+ # contain "{{filename}}", but doesn't support "{{line}}".
+ open: ""
+
+ # Command for opening a link. Should contain "{{link}}".
+ openLink: ""
+
+ # EditCommand is the command for editing a file.
+ # Deprecated: use Edit instead. Note that semantics are different:
+ # EditCommand is just the command itself, whereas Edit contains a
+ # "{{filename}}" variable.
+ editCommand: ""
+
+ # EditCommandTemplate is the command template for editing a file
+ # Deprecated: use EditAtLine instead.
+ editCommandTemplate: ""
+
+ # OpenCommand is the command for opening a file
+ # Deprecated: use Open instead.
+ openCommand: ""
+
+ # OpenLinkCommand is the command for opening a link
+ # Deprecated: use OpenLink instead.
+ openLinkCommand: ""
+
+ # CopyToClipboardCmd is the command for copying to clipboard.
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
+ copyToClipboardCmd: ""
+
+ # ReadFromClipboardCmd is the command for reading the clipboard.
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
+ readFromClipboardCmd: ""
+
+# If true, don't display introductory popups upon opening Lazygit.
disableStartupPopups: false
-notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip' | 'quit'
-promptToReturnFromSubprocess: true # display confirmation when subprocess terminates
+
+# What to do when opening Lazygit outside of a git repo.
+# - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
+# - 'create': initialize a new repo
+# - 'skip': open most recent repo
+# - 'quit': exit Lazygit
+notARepository: prompt
+
+# If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
+promptToReturnFromSubprocess: true
+
+# Keybindings
keybinding:
universal:
- quit: 'q'
- quit-alt1: '' # alternative/alias of quit
- return: '' # return to previous menu, will quit if there's nowhere to return
- quitWithoutChangingDirectory: 'Q'
- togglePanel: '' # goto the next panel
- prevItem: '' # go one line up
- nextItem: '' # go one line down
- prevItem-alt: 'k' # go one line up
- nextItem-alt: 'j' # go one line down
- prevPage: ',' # go to next page in list
- nextPage: '.' # go to previous page in list
- gotoTop: '<' # go to top of list
- gotoBottom: '>' # go to bottom of list
- scrollLeft: 'H' # scroll left within list view
- scrollRight: 'L' # scroll right within list view
- prevBlock: '' # goto the previous block / panel
- nextBlock: '' # goto the next block / panel
- prevBlock-alt: 'h' # goto the previous block / panel
- nextBlock-alt: 'l' # goto the next block / panel
- jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
- nextMatch: 'n'
- prevMatch: 'N'
- optionMenu: # show help menu
- optionMenu-alt1: '?' # show help menu
- select: ''
- goInto: ''
- openRecentRepos: ''
- confirm: ''
- remove: 'd'
- new: 'n'
- edit: 'e'
- openFile: 'o'
- scrollUpMain: '' # main panel scroll up
- scrollDownMain: '' # main panel scroll down
- scrollUpMain-alt1: 'K' # main panel scroll up
- scrollDownMain-alt1: 'J' # main panel scroll down
- scrollUpMain-alt2: '' # main panel scroll up
- scrollDownMain-alt2: '' # main panel scroll down
+ quit: q
+ quit-alt1:
+ return:
+ quitWithoutChangingDirectory: Q
+ togglePanel:
+ prevItem:
+ nextItem:
+ prevItem-alt: k
+ nextItem-alt: j
+ prevPage: ','
+ nextPage: .
+ scrollLeft: H
+ scrollRight: L
+ gotoTop: <
+ gotoBottom: '>'
+ toggleRangeSelect: v
+ rangeSelectDown:
+ rangeSelectUp:
+ prevBlock:
+ nextBlock:
+ prevBlock-alt: h
+ nextBlock-alt: l
+ nextBlock-alt2:
+ prevBlock-alt2:
+ jumpToBlock:
+ - "1"
+ - "2"
+ - "3"
+ - "4"
+ - "5"
+ nextMatch: "n"
+ prevMatch: "N"
+ startSearch: /
+ optionMenu:
+ optionMenu-alt1: '?'
+ select:
+ goInto:
+ confirm:
+ confirmInEditor:
+ remove: d
+ new: "n"
+ edit: e
+ openFile: o
+ scrollUpMain:
+ scrollDownMain:
+ scrollUpMain-alt1: K
+ scrollDownMain-alt1: J
+ scrollUpMain-alt2:
+ scrollDownMain-alt2:
executeCustomCommand: ':'
- createRebaseOptionsMenu: 'm'
- pushFiles: 'P'
- pullFiles: 'p'
- refresh: 'R'
- createPatchOptionsMenu: ''
+ createRebaseOptionsMenu: m
+
+ # 'Files' appended for legacy reasons
+ pushFiles: P
+
+ # 'Files' appended for legacy reasons
+ pullFiles: p
+ refresh: R
+ createPatchOptionsMenu:
nextTab: ']'
prevTab: '['
- nextScreenMode: '+'
- prevScreenMode: '_'
- undo: 'z'
- redo: ''
- filteringMenu: ''
- diffingMenu: 'W'
- diffingMenu-alt: '' # deprecated
- copyToClipboard: ''
- submitEditorText: ''
+ nextScreenMode: +
+ prevScreenMode: _
+ undo: z
+ redo:
+ filteringMenu:
+ diffingMenu: W
+ diffingMenu-alt:
+ copyToClipboard:
+ openRecentRepos:
+ submitEditorText:
extrasMenu: '@'
- toggleWhitespaceInDiffView: ''
+ toggleWhitespaceInDiffView:
increaseContextInDiffView: '}'
decreaseContextInDiffView: '{'
- toggleRangeSelect: 'v'
- rangeSelectUp: ''
- rangeSelectDown: ''
+ increaseRenameSimilarityThreshold: )
+ decreaseRenameSimilarityThreshold: (
+ openDiffTool:
status:
- checkForUpdate: 'u'
- recentRepos: ''
+ checkForUpdate: u
+ recentRepos:
+ allBranchesLogGraph: a
files:
- commitChanges: 'c'
- commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
- amendLastCommit: 'A'
- commitChangesWithEditor: 'C'
- findBaseCommitForFixup: ''
- confirmDiscard: 'x'
- ignoreFile: 'i'
- refreshFiles: 'r'
- stashAllChanges: 's'
- viewStashOptions: 'S'
- toggleStagedAll: 'a' # stage/unstage all
- viewResetOptions: 'D'
- fetch: 'f'
+ commitChanges: c
+ commitChangesWithoutHook: w
+ amendLastCommit: A
+ commitChangesWithEditor: C
+ findBaseCommitForFixup:
+ confirmDiscard: x
+ ignoreFile: i
+ refreshFiles: r
+ stashAllChanges: s
+ viewStashOptions: S
+ toggleStagedAll: a
+ viewResetOptions: D
+ fetch: f
toggleTreeView: '`'
- openMergeTool: 'M'
- openStatusFilter: ''
+ openMergeTool: M
+ openStatusFilter:
+ copyFileInfoToClipboard: "y"
branches:
- createPullRequest: 'o'
- viewPullRequestOptions: 'O'
- checkoutBranchByName: 'c'
- forceCheckoutBranch: 'F'
- rebaseBranch: 'r'
- renameBranch: 'R'
- mergeIntoCurrentBranch: 'M'
- viewGitFlowOptions: 'i'
- fastForward: 'f' # fast-forward this branch from its upstream
- createTag: 'T'
- pushTag: 'P'
- setUpstream: 'u' # set as upstream of checked-out branch
- fetchRemote: 'f'
+ createPullRequest: o
+ viewPullRequestOptions: O
+ copyPullRequestURL:
+ checkoutBranchByName: c
+ forceCheckoutBranch: F
+ rebaseBranch: r
+ renameBranch: R
+ mergeIntoCurrentBranch: M
+ viewGitFlowOptions: i
+ fastForward: f
+ createTag: T
+ pushTag: P
+ setUpstream: u
+ fetchRemote: f
+ sortOrder: s
+ worktrees:
+ viewWorktreeOptions: w
commits:
- squashDown: 's'
- renameCommit: 'r'
- renameCommitWithEditor: 'R'
- viewResetOptions: 'g'
- markCommitAsFixup: 'f'
- createFixupCommit: 'F' # create fixup commit for this commit
- squashAboveCommits: 'S'
- moveDownCommit: '' # move commit down one
- moveUpCommit: '' # move commit up one
- amendToCommit: 'A'
- amendAttributeMenu: 'a'
- pickCommit: 'p' # pick commit (when mid-rebase)
- revertCommit: 't'
- cherryPickCopy: 'C'
- pasteCommits: 'V'
- tagCommit: 'T'
- checkoutCommit: ''
- resetCherryPick: ''
- copyCommitMessageToClipboard: ''
- openLogMenu: ''
- viewBisectOptions: 'b'
+ squashDown: s
+ renameCommit: r
+ renameCommitWithEditor: R
+ viewResetOptions: g
+ markCommitAsFixup: f
+ createFixupCommit: F
+ squashAboveCommits: S
+ moveDownCommit:
+ moveUpCommit:
+ amendToCommit: A
+ resetCommitAuthor: a
+ pickCommit: p
+ revertCommit: t
+ cherryPickCopy: C
+ pasteCommits: V
+ markCommitAsBaseForRebase: B
+ tagCommit: T
+ checkoutCommit:
+ resetCherryPick:
+ copyCommitAttributeToClipboard: "y"
+ openLogMenu:
+ openInBrowser: o
+ viewBisectOptions: b
+ startInteractiveRebase: i
+ amendAttribute:
+ resetAuthor: a
+ setAuthor: A
+ addCoAuthor: c
stash:
- popStash: 'g'
- renameStash: 'r'
+ popStash: g
+ renameStash: r
commitFiles:
- checkoutCommitFile: 'c'
+ checkoutCommitFile: c
main:
- toggleSelectHunk: 'a'
- pickBothHunks: 'b'
+ toggleSelectHunk: a
+ pickBothHunks: b
+ editSelectHunk: E
submodules:
- init: 'i'
- update: 'u'
- bulkMenu: 'b'
+ init: i
+ update: u
+ bulkMenu: b
commitMessage:
- commitMenu: ''
- amendAttribute:
- addCoAuthor: 'c'
- resetAuthor: 'a'
- setAuthor: 'A'
+ commitMenu:
```
+
## Platform Defaults
@@ -315,7 +634,7 @@ os:
open: 'open {{filename}}'
```
-## Custom Command for Copying to Clipboard
+## Custom Command for Copying to and Pasting from Clipboard
```yaml
os:
copyToClipboardCmd: ''
@@ -328,6 +647,12 @@ os:
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty
```
+A custom command for reading from the clipboard can be set using
+```yaml
+os:
+ readFromClipboardCmd: ''
+```
+It is used, for example, when pasting a commit message into the commit message panel. The command is supposed to output the clipboard content to stdout.
## Configuring File Editing
@@ -566,6 +891,21 @@ git:
replace: '[$1] '
```
+## Predefined branch name prefix
+
+In situations where certain naming pattern is used for branches, this can be used to populate new branch creation with a static prefix.
+
+Example:
+
+Some branches:
+- jsmith/AB-123
+- cwilson/AB-125
+
+```yaml
+git:
+ branchPrefix: "firstlast/"
+```
+
## Custom git log command
You can override the `git log` command that's used to render the log of the selected branch like so:
diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md
index 426d6f8f699..1053f9e4579 100644
--- a/docs/Custom_Command_Keybindings.md
+++ b/docs/Custom_Command_Keybindings.md
@@ -59,6 +59,7 @@ For a given custom command, here are the allowed fields:
| description | Label for the custom command when displayed in the keybindings menu | no |
| stream | Whether you want to stream the command's output to the Command Log panel | no |
| showOutput | Whether you want to show the command's output in a popup within Lazygit | no |
+| outputTitle | The title to display in the popup panel if showOutput is true. If left unset, the command will be used as the title. | no |
| after | Actions to take after the command has completed | no |
Here are the options for the `after` key:
diff --git a/docs/Fixup_Commits.md b/docs/Fixup_Commits.md
index fde85ee39e4..d08914f334a 100644
--- a/docs/Fixup_Commits.md
+++ b/docs/Fixup_Commits.md
@@ -56,22 +56,10 @@ base commit in the Commits view automatically. From there, you can either press
shift-F to create a fixup commit for it, or shift-A to amend your changes into
the commit if you haven't published your branch yet.
-This command works in many cases, and when it does it almost feels like magic,
-but it's important to understand its limitations because it doesn't always work.
-The way it works is that it looks at the deleted lines of your current
-modifications, blames them to find out which commit those lines come from, and
-if they all come from the same commit, it selects it. So here are cases where it
-doesn't work:
-
-- Your current diff has only added lines, but no deleted lines. In this case
- there's no way for lazygit to know which commit you want to add them to.
-- The deleted lines belong to multiple different commits. In this case you can
- help lazygit by staging a set of files or hunks that all belong to the same
- commit; if some changes are staged, the ctrl-f command works only on those.
-- The found commit is already on master; in this case, lazygit refuses to select
- it, because it doesn't make sense to create fixups for it, let alone amend to
- it.
-
-To sum it up: the command works great if you are changing code again that you
-changed or added earlier in the same branch. This is a common enough case to
-make the command useful.
+If you have many modifications in your working copy, it is a good idea to stage
+related changes that are meant to go into the same fixup commit; if no changes
+are staged, ctrl-f works on all unstaged modifications, and then it might show
+an error if it finds multiple different base commits. If you are interested in
+what the command does to do its magic, and how you can help it work better, you
+may want to read the [design document](dev/Find_Base_Commit_For_Fixup_Design.md)
+that describes this.
diff --git a/docs/README.md b/docs/README.md
index d840637a045..1bc0bb6be2e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,11 +1,11 @@
-# Documentation Overview
-
-* [Configuration](./Config.md).
-* [Custom Commands](./Custom_Command_Keybindings.md)
-* [Custom Pagers](./Custom_Pagers.md)
-* [Dev docs](./dev)
-* [Keybindings](./keybindings)
-* [Undo/Redo](./Undoing.md)
-* [Range Select](./Range_Select.md)
-* [Searching/Filtering](./Searching.md)
-* [Stacked Branches](./Stacked_Branches.md)
+# Documentation Overview
+
+* [Configuration](./Config.md).
+* [Custom Commands](./Custom_Command_Keybindings.md)
+* [Custom Pagers](./Custom_Pagers.md)
+* [Dev docs](./dev)
+* [Keybindings](./keybindings)
+* [Undo/Redo](./Undoing.md)
+* [Range Select](./Range_Select.md)
+* [Searching/Filtering](./Searching.md)
+* [Stacked Branches](./Stacked_Branches.md)
diff --git a/docs/dev/Find_Base_Commit_For_Fixup_Design.md b/docs/dev/Find_Base_Commit_For_Fixup_Design.md
new file mode 100644
index 00000000000..4eff43b31fd
--- /dev/null
+++ b/docs/dev/Find_Base_Commit_For_Fixup_Design.md
@@ -0,0 +1,229 @@
+# About the mechanics of lazygit's "Find base commit for fixup" command
+
+## Background
+
+Lazygit has a command called "Find base commit for fixup" that helps with
+creating fixup commits. (It is bound to "ctrl-f" by default, and I'll call it
+simply "the ctrl-f command" throughout the rest of this text for brevity.)
+
+It's a heuristic that needs to make a few assumptions; it tends to work well in
+practice if users are aware of its limitations. The user-facing side of the
+topic is explained [here](../Fixup_Commits.md). In this document we describe how
+it works internally, and the design decisions behind it.
+
+It is also interesting to compare it to the standalone tool
+[git-absorb](https://github.com/tummychow/git-absorb) which does a very similar
+thing, but made different decisions in some cases. We'll explore these
+differences in this document.
+
+## Design goals
+
+I'll start with git-absorb's design goals (my interpretation, since I can't
+speak for git-absorb's maintainer of course): its main goal seems to be minimum
+user interaction required. The idea is that you have a PR in review, the
+reviewer requested a bunch of changes, you make all these changes, so you have a
+working copy with lots of modified files, and then you fire up git-absorb and it
+creates all the necessary fixup commits automatically with no further user
+intervention.
+
+While this sounds attractive, it conflicts with ctrl-f's main design goal, which
+is to support creating high-quality fixups. My philosophy is that fixup commits
+should have the same high quality standards as normal commits; in particular:
+
+- they should be atomic. This means that multiple diff hunks that belong
+ together to form one logical change should be in the same fixup commit. (Not
+ always possible if the logical change needs to be fixed up into several
+ different base commits.)
+- they should be minimal. Every fixup commit should ideally contain only one
+ logical change, not several unrelated ones.
+
+Why is this important? Because fixup commits are mainly a tool for reviewing (if
+they weren't, you might as well squash the changes into their base commits right
+away). And reviewing fixup commits is easier if they are well-structured, just
+like normal commits.
+
+The only way to achieve this with git-absorb is to set the `oneFixupPerCommit`
+config option (for the first goal), and then manually stage the changes that
+belong together (for the second). This is close to what you have to do with
+ctrl-f, with one exception that we'll get to below.
+
+But ctrl-f enforces this by refusing to do the job if the staged hunks belong to
+more than one base commit. Git-absorb will happily create multiple fixup commits
+in this case; ctrl-f doesn't, to enforce that you pay attention to how you group
+the changes. There's another reason for this behavior: ctrl-f doesn't create
+fixup commits itself (unlike git-absorb), instead it just selects the found base
+commit so that the user can decide whether to amend the changes right in, or
+create a fixup commit from there (both are single-key commands in lazygit). And
+lazygit doesn't support non-contiguous multiselections of commits, but even if
+it did, it wouldn't help much in this case.
+
+## The mechanics
+
+### General approach
+
+Git-absorb uses a relatively simple approach, and the benefit is of course that
+it is easy to understand: it looks at every diff hunk separately, and for every
+hunk it looks at all commits (starting from the newest one backwards) to find
+the earliest commit that the change can be amended to without conflicts.
+
+It is important to realize that "diff hunk" doesn't necessarily mean what you
+see in the diff view. Git-absorb and ctrl-f both use a context of 0 when diffing
+your code, so they often see more and smaller hunks than users do. For example,
+moving a line of code down by one line is a single hunk for users, but it's two
+separate hunks for git-absorb and ctrl-f; one for deleting the line at the old
+place, and another one for adding the line at the new place, even if it's only
+one line further down.
+
+From this, it follows that there's one big problem with git-absorb's approach:
+when moving code, it doesn't realize that the two related hunks of deleting the
+code from the old place and inserting it at the new place belong together, and
+often it will manage to create a fixup commit for the first hunk, but leave the
+other hunk in your working copy as "don't know what to do with this". As an
+example, suppose your PR is adding a line of code to an existing function, maybe
+one that declares a new variable, and a reviewer suggests to move this line down
+a bit, closer to where some other related variables are declared. Moving the
+line down results in two diff hunks (from the perspective of git-absorb and
+ctrl-f, as they both use a context of 0 when diffing), and when looking at the
+second diff hunk in isolation there's no way to find a base commit in your PR
+for it, because the surrounding code is already on main.
+
+To solve this, the ctrl-f command makes a distinction between hunks that have
+deleted lines and hunks that have only added lines. If the whole diff contains
+any hunks that have deleted lines, it uses only those hunks to determine the
+base commit, and then assumes that all the hunks that have only added lines
+belong into the same commit. This nicely solves the above example of moving
+code, but also other examples such as the following:
+
+
+Click to show example
+
+Suppose you have a PR in which you added the following function:
+
+```go
+func findCommit(hash string) (*models.Commit, int, bool) {
+ for i, commit := range self.c.Model().Commits {
+ if commit.Hash == hash {
+ return commit, i, true
+ }
+ }
+
+ return nil, -1, false
+}
+```
+
+A reviewer suggests to replace the manual `for` loop with a call to
+`lo.FindIndexOf` since that's less code and more idiomatic. So your modification
+is this:
+
+```diff
+--- a/my_file.go
++++ b/my_file.go
+@@ -12,2 +12,3 @@ import (
+ "github.com/jesseduffield/lazygit/pkg/utils"
++ "github.com/samber/lo"
+ "golang.org/x/sync/errgroup"
+@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, erro
+ func findCommit(hash string) (*models.Commit, int, bool) {
+- for i, commit := range self.c.Model().Commits {
+- if commit.Hash == hash {
+- return commit, i, true
+- }
+- }
+-
+- return nil, -1, false
++ return lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
++ return commit.Hash == hash
++ })
+ }
+```
+
+If we were to look at these two hunks separately, we'd easily find the base
+commit for the second one, but we wouldn't find the one for the first hunk
+because the imports around the added import have been on main for a long time.
+In fact, git-absorb leaves this hunk in the working copy because it doesn't know
+what to do with it.
+
+
+
+Only if there are no hunks with deleted lines does ctrl-f look at the hunks with
+only added lines and determines the base commit for them. This solves cases like
+adding a comment above a function that you added in your PR.
+
+The downside of this more complicated approach is that it relies on the user
+staging related hunks correctly. However, in my experience this is easy to do
+and not very error-prone, as long as users are aware of this behavior. Lazygit
+tries to help making them aware of it by showing a warning whenever there are
+hunks with only added lines in addition to hunks with deleted lines.
+
+### Finding the base commit for a given hunk
+
+As explained above, git-absorb finds the base commit by walking the commits
+backwards until it finds one that conflicts with the hunk, and then the found
+base commit is the one just before that one. This works reliably, but it is
+slow.
+
+Ctrl-f uses a different approach that is usually much faster, but should always
+yield the same result. Again, it makes a distinction between hunks with deleted
+lines and hunks with only added lines. For hunks with deleted lines it performs
+a line range blame for all the deleted lines (e.g. `git blame -L42,+3 --
+filename`), and if the result is the same for all deleted lines, then that's the
+base commit; otherwise it returns an error.
+
+For hunks with only added lines, it gets a little more complicated. We blame the
+single lines just before and just after the hunk (I'll ignore the edge cases of
+either of those not existing because the hunk is at the beginning or end of the
+file; read the code to see how we handle these cases). If the blame result is
+the same for both, then that's the base commit. This is the case of adding a
+line in the middle of a block of code that was added in the PR. Otherwise, the
+base commit is the more recent of the two (and in this case it doesn't matter if
+the other one is an earlier commit in the current branch, or a possibly very old
+commit that's already on main). This covers the common case of adding a comment
+to a function that was added in the PR, but also adding another line at the end
+of a block of code that was added in the base commit.
+
+It's interesting to discuss what "more recent" means here. You could say if
+commit A is an ancestor of commit B (or in other words, A is reachable from B)
+then B is the more recent one. And if none of the two commits is reachable from
+the other, you have an error case because it's unclear which of the two should
+be considered the base commit. The scenario in which this happens is a commit
+history like this:
+
+```
+ C---D
+ / \
+A---B---E---F---G
+```
+
+where, for instance, D and E are the two blame results.
+
+Unfortunately, determining the ancestry relationship between two commits using
+git commands is a bit expensive and not totally straightforward. Fortunately,
+it's not necessary in lazygit because lazygit has the most recent 300 commits
+cached in memory, and can simply search its linear list of commits to see which
+one is closer to the beginning of the list. If only one of the two commits is
+found within those 300 commits, then that's the more recent one; if neither is
+found, we assume that both commits are on main and error out. In the merge
+scenario pictured above, we arbitrarily return one of the two commits (this will
+depend on the log order), but that's probably fine as this scenario should be
+extremely rare in practice; in most cases, feature branches are simply linear.
+
+### Knowing where to stop searching
+
+Git-absorb needs to know when to stop walking backwards searching for commits,
+since it doesn't make sense to create fixups for commits that are already on
+main. However, it doesn't know where the current branch ends and main starts, so
+it needs to rely on user input for this. By default it searches the most recent
+10 commits, but this can be overridden with a config setting. In longer branches
+this is often not enough for finding the base commit; but setting it to a higher
+value causes the command to take longer to complete when the base commit can't
+be found.
+
+Lazygit doesn't have this problem. For a given blame result it needs to
+determine whether that commit is already on main, and if it can find the commit
+in its cached list of the first 300 commits it can get that information from
+there, because lazygit knows what the user's configured main branches are
+(`master` and `main` by default, but it could also include branches like `devel`
+or `1.0-hotfixes`), and so it can tell for each commit whether it's contained in
+one of those main branches. And if it can't find it among the first 300 commits,
+it assumes the commit already on main, on the assumption that no feature branch
+has more than 300 commits.
diff --git a/docs/dev/Profiling.md b/docs/dev/Profiling.md
new file mode 100644
index 00000000000..bfdffe4f909
--- /dev/null
+++ b/docs/dev/Profiling.md
@@ -0,0 +1,69 @@
+# Profiling Lazygit
+
+If you want to investigate what's contributing to CPU or memory usage, start
+lazygit with the `-profile` command line flag. This tells it to start an
+integrated web server that listens for profiling requests.
+
+## Save profile data
+
+### CPU
+
+While lazygit is running with the `-profile` flag, perform a CPU profile and
+save it to a file by running this command in another terminal window:
+
+```sh
+curl -o cpu.out http://127.0.0.1:6060/debug/pprof/profile
+```
+
+By default, it profiles for 30 seconds. To change the duration, use
+
+```sh
+curl -o cpu.out 'http://127.0.0.1:6060/debug/pprof/profile?seconds=60'
+```
+
+### Memory
+
+To save a heap profile (containing information about all memory allocated so
+far since startup), use
+
+```sh
+curl -o mem.out http://127.0.0.1:6060/debug/pprof/heap
+```
+
+Sometimes it can be useful to get a delta log, i.e. to see how memory usage
+developed from one point in time to another. For that, use
+
+```sh
+curl -o mem.out 'http://127.0.0.1:6060/debug/pprof/heap?seconds=20'
+```
+
+This will log the memory usage difference between now and 20 seconds later, so
+it gives you 20 seconds to perform the action in lazygit that you are interested
+in measuring.
+
+## View profile data
+
+To display the profile data, you can either use speedscope.app, or the pprof
+tool that comes with go. I prefer the former because it has a nicer UI and is a
+little more powerful; however, I have seen cases where it wasn't able to load a
+profile for some reason, in which case it's good to have the pprof tool as a
+fallback.
+
+### Speedscope.app
+
+Go to https://www.speedscope.app/ in your browser, and drag the saved profile
+onto the browser window. Refer to [the
+documentation](https://github.com/jlfwong/speedscope?tab=readme-ov-file#usage)
+for how to navigate the data.
+
+### Pprof tool
+
+To view a profile that you saved as `cpu.out`, use
+
+```sh
+go tool pprof -http=:8080 cpu.out
+```
+
+By default this shows the graph view, which I don't find very useful myself.
+Choose "Flame Graph" from the View menu to show a much more useful
+representation of the data.
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 44534741780..fcfcf2741ea 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -4,3 +4,5 @@
* [Busy/Idle Tracking](./Busy.md)
* [Integration Tests](../../pkg/integration/README.md)
* [Demo Recordings](./Demo_Recordings.md)
+* [Find base commit for fixup design](Find_Base_Commit_For_Fixup_Design.md)
+* [Profiling](Profiling.md)
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index 8edeb42bccb..47ae9cfb5d7 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -14,6 +14,8 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -80,7 +82,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` `` | Reset copied (cherry-picked) commits selection | |
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | Reword | Reword the selected commit's message. |
| `` R `` | Reword with editor | |
| `` d `` | Drop | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -162,13 +164,14 @@ If you would instead like to start an interactive rebase from the selected commi
| `` F `` | Force checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | Merge | Merge selected branch into currently checked out branch. |
+| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Fast-forward | Fast-forward selected branch from its upstream. |
| `` T `` | New tag | |
| `` s `` | Sort order | |
| `` g `` | Reset | |
| `` R `` | Rename branch | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -265,12 +268,13 @@ If you would instead like to start an interactive rebase from the selected commi
| `` `` | Copy branch name to clipboard | |
| `` `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | New branch | |
-| `` M `` | Merge | Merge selected branch into currently checked out branch. |
+| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
| `` s `` | Sort order | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -307,7 +311,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` e `` | Edit config file | Open file in external editor. |
| `` u `` | Check for update | |
| `` `` | Switch to a recent repo | |
-| `` a `` | Show all branch logs | |
+| `` a `` | Show/cycle all branch logs | |
## Sub-commits
@@ -349,6 +353,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index 359972acecb..d72096f62cd 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -14,6 +14,8 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` @ `` | コマンドログメニューを開く | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | カスタムコマンドを実行 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -97,7 +99,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` `` | Reset copied (cherry-picked) commits selection | |
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | コミットメッセージを変更 | Reword the selected commit's message. |
| `` R `` | エディタでコミットメッセージを編集 | |
| `` d `` | コミットを削除 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -183,6 +185,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | タグをpush | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -232,13 +235,14 @@ If you would instead like to start an interactive rebase from the selected commi
| `` F `` | Force checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | 現在のブランチにマージ | Merge selected branch into currently checked out branch. |
+| `` M `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Fast-forward | Fast-forward selected branch from its upstream. |
| `` T `` | タグを作成 | |
| `` s `` | 並び替え | |
| `` g `` | Reset | |
| `` R `` | ブランチ名を変更 | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -329,12 +333,13 @@ If you would instead like to start an interactive rebase from the selected commi
| `` `` | ブランチ名をクリップボードにコピー | |
| `` `` | チェックアウト | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 新しいブランチを作成 | |
-| `` M `` | 現在のブランチにマージ | Merge selected branch into currently checked out branch. |
+| `` M `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
| `` s `` | 並び替え | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index 1313ad9d3e4..7c4e9c9f458 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -14,6 +14,8 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` @ `` | 명령어 로그 메뉴 열기 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 푸시 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 업데이트 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기 | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -189,13 +191,14 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` F `` | 강제 체크아웃 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | 현재 브랜치에 병합 | Merge selected branch into currently checked out branch. |
+| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Fast-forward this branch from its upstream | Fast-forward selected branch from its upstream. |
| `` T `` | 태그를 생성 | |
| `` s `` | Sort order | |
| `` g `` | View reset options | |
| `` R `` | 브랜치 이름 변경 | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -242,12 +245,13 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` `` | 브랜치명을 클립보드에 복사 | |
| `` `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 새 브랜치 생성 | |
-| `` M `` | 현재 브랜치에 병합 | Merge selected branch into currently checked out branch. |
+| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
| `` s `` | Sort order | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -260,7 +264,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` `` | Reset cherry-picked (copied) commits selection | |
| `` b `` | Bisect 옵션 보기 | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. |
| `` R `` | 에디터에서 커밋메시지 수정 | |
| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -322,6 +326,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index 542786192fc..60cbf54e9b9 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -14,6 +14,8 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Voer aangepaste commando uit | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -101,13 +103,14 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | Merge in met huidige checked out branch | Merge selected branch into currently checked out branch. |
+| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. |
| `` T `` | Creëer tag | |
| `` s `` | Sort order | |
| `` g `` | Bekijk reset opties | |
| `` R `` | Hernoem branch | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -143,7 +146,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` `` | Reset cherry-picked (gekopieerde) commits selectie | |
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | Hernoem commit | Reword the selected commit's message. |
| `` R `` | Hernoem commit met editor | |
| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -243,12 +246,13 @@ If you would instead like to start an interactive rebase from the selected commi
| `` `` | Kopieer branch name naar klembord | |
| `` `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | Nieuwe branch | |
-| `` M `` | Merge in met huidige checked out branch | Merge selected branch into currently checked out branch. |
+| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch |
| `` s `` | Sort order | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -349,6 +353,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index e41c07e4d0e..7d2d7e5617c 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -14,6 +14,8 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_
| `` @ `` | Pokaż opcje dziennika poleceń | Pokaż opcje dla dziennika poleceń, np. pokazywanie/ukrywanie dziennika poleceń i skupienie na dzienniku poleceń. |
| `` P `` | Wypchnij | Wypchnij bieżącą gałąź do jej gałęzi nadrzędnej. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej. |
| `` p `` | Pociągnij | Pociągnij zmiany z zdalnego dla bieżącej gałęzi. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Zwiększ rozmiar kontekstu w widoku różnic | Zwiększ ilość kontekstu pokazywanego wokół zmian w widoku różnic. |
| `` { `` | Zmniejsz rozmiar kontekstu w widoku różnic | Zmniejsz ilość kontekstu pokazywanego wokół zmian w widoku różnic. |
| `` : `` | Wykonaj polecenie niestandardowe | Wyświetl monit, w którym możesz wprowadzić polecenie powłoki do wykonania. Nie należy mylić z wcześniej skonfigurowanymi poleceniami niestandardowymi. |
@@ -134,6 +136,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
| `` g `` | Reset | |
| `` R `` | Zmień nazwę gałęzi | |
| `` u `` | Pokaż opcje upstream | Pokaż opcje dotyczące upstream gałęzi, np. ustawianie/usuwanie upstream i resetowanie do upstream. |
+| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
@@ -331,6 +334,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. |
| `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
+| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
@@ -359,6 +363,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
| `` u `` | Ustaw jako upstream | Ustaw wybraną gałąź zdalną jako upstream sprawdzonej gałęzi. |
| `` s `` | Kolejność sortowania | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
+| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index c83652ee980..ab49f2ab370 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -14,6 +14,8 @@ _Связки клавиш_
| `` @ `` | Открыть меню журнала команд | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | Отправить изменения | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | Получить и слить изменения | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Выполнить пользовательскую команду | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -144,7 +146,7 @@ _Связки клавиш_
| `` `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | |
| `` b `` | Просмотреть параметры бинарного поиска | |
| `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | Перефразировать коммит | Reword the selected commit's message. |
| `` R `` | Переписать коммит с помощью редактора | |
| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -189,13 +191,14 @@ If you would instead like to start an interactive rebase from the selected commi
| `` F `` | Принудительное переключение | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | Слияние с текущей переключённой веткой | Merge selected branch into currently checked out branch. |
+| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Перемотать эту ветку вперёд из её upstream-ветки | Fast-forward selected branch from its upstream. |
| `` T `` | Создать тег | |
| `` s `` | Порядок сортировки | |
| `` g `` | Просмотреть параметры сброса | |
| `` R `` | Переименовать ветку | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -288,6 +291,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -299,12 +303,13 @@ If you would instead like to start an interactive rebase from the selected commi
| `` `` | Скопировать название ветки в буфер обмена | |
| `` `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | Новая ветка | |
-| `` M `` | Слияние с текущей переключённой веткой | Merge selected branch into currently checked out branch. |
+| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | Установить как upstream-ветку переключённую ветку |
| `` s `` | Порядок сортировки | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index e0f3a819c2e..c91cdc8e874 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -14,6 +14,8 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` @ `` | 打开命令日志菜单 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | 执行自定义命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -91,13 +93,14 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` F `` | 强制检出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
+| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 从上游快进此分支 | Fast-forward selected branch from its upstream. |
| `` T `` | 创建标签 | |
| `` s `` | Sort order | |
| `` g `` | 查看重置选项 | |
| `` R `` | 重命名分支 | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -141,7 +144,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` `` | 重置已拣选(复制)的提交 | |
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | 修正(fixup) | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | 修正(fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | 改写提交 | Reword the selected commit's message. |
| `` R `` | 使用编辑器重命名提交 | |
| `` d `` | 删除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -248,6 +251,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | 推送标签 | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -342,12 +346,13 @@ If you would instead like to start an interactive rebase from the selected commi
| `` `` | 将分支名称复制到剪贴板 | |
| `` `` | 检出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 新分支 | |
-| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
+| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | 设置为检出分支的上游 |
| `` s `` | Sort order | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 5457b05d233..dccba00dccf 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -14,6 +14,8 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
+| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
+| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | 執行自訂命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
@@ -166,7 +168,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B
| `` `` | 重設選定的揀選 (複製) 提交 | |
| `` b `` | 查看二分選項 | |
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
-| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded. |
+| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | 改寫提交 | Reword the selected commit's message. |
| `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -264,13 +266,14 @@ If you would instead like to start an interactive rebase from the selected commi
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
-| `` M `` | 合併到當前檢出的分支 | Merge selected branch into currently checked out branch. |
+| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 從上游快進此分支 | Fast-forward selected branch from its upstream. |
| `` T `` | 建立標籤 | |
| `` s `` | Sort order | |
| `` g `` | 檢視重設選項 | |
| `` R `` | 重新命名分支 | |
| `` u `` | 檢視上游設定 | 檢視有關上游分支的設定(例如重設至上游) |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -284,6 +287,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -353,12 +357,13 @@ If you would instead like to start an interactive rebase from the selected commi
| `` `` | 複製分支名稱到剪貼簿 | |
| `` `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 新分支 | |
-| `` M `` | 合併到當前檢出的分支 | Merge selected branch into currently checked out branch. |
+| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | 將此分支設為當前分支之上游 |
| `` s `` | Sort order | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
diff --git a/go.mod b/go.mod
index ca0adebe212..bc8009a986b 100644
--- a/go.mod
+++ b/go.mod
@@ -11,11 +11,12 @@ require (
github.com/gdamore/tcell/v2 v2.7.4
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.4.2
+ github.com/iancoleman/orderedmap v0.3.0
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
- github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513
+ github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -37,6 +38,7 @@ require (
github.com/stretchr/testify v1.8.1
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
+ golang.org/x/sync v0.7.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -73,8 +75,8 @@ require (
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.7.0 // indirect
- golang.org/x/sys v0.19.0 // indirect
- golang.org/x/term v0.19.0 // indirect
- golang.org/x/text v0.14.0 // indirect
+ golang.org/x/sys v0.21.0 // indirect
+ golang.org/x/term v0.21.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
diff --git a/go.sum b/go.sum
index cdd64127c1e..e575afcadba 100644
--- a/go.sum
+++ b/go.sum
@@ -171,6 +171,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
+github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@@ -186,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
-github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513 h1:Y1bw5iItrsDCumATc/rklIJ/6K+68ieiWZJedhrNuXo=
-github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
+github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b h1:oxCq0DvR2GMGf4UaoaASb0nQK/fJMQW3c3PNCLWCjS8=
+github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -421,6 +423,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -470,14 +474,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
-golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
-golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
-golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
+golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
+golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -487,8 +491,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/pkg/app/app.go b/pkg/app/app.go
index a16fbcc1fe7..e12461e2803 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -14,7 +14,6 @@ import (
"github.com/spf13/afero"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
- "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
@@ -119,7 +118,14 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
return app, err
}
- showRecentRepos, err := app.setupRepo()
+ // If we're not in a repo, repoPaths will be nil. The error is moot for us
+ // at this stage, since we'll try to init a new repo in setupRepo(), below
+ repoPaths, err := git_commands.GetRepoPaths(app.OSCommand.Cmd, gitVersion)
+ if err != nil {
+ return app, err
+ }
+
+ showRecentRepos, err := app.setupRepo(repoPaths)
if err != nil {
return app, err
}
@@ -168,14 +174,16 @@ func openRecentRepo(app *App) bool {
return false
}
-func (app *App) setupRepo() (bool, error) {
+func (app *App) setupRepo(
+ repoPaths *git_commands.RepoPaths,
+) (bool, error) {
if env.GetGitDirEnv() != "" {
- // we've been given the git dir directly. We'll verify this dir when initializing our Git object
+ // we've been given the git dir directly. Skip setup
return false, nil
}
// if we are not in a git repo, we ask if we want to `git init`
- if err := commands.VerifyInGitRepo(app.OSCommand); err != nil {
+ if repoPaths == nil {
cwd, err := os.Getwd()
if err != nil {
return false, err
@@ -221,6 +229,7 @@ func (app *App) setupRepo() (bool, error) {
if err := app.OSCommand.Cmd.New(args).Run(); err != nil {
return false, err
}
+
return false, nil
}
@@ -238,10 +247,7 @@ func (app *App) setupRepo() (bool, error) {
}
// Run this afterward so that the previous repo creation steps can run without this interfering
- if isBare, err := git_commands.IsBareRepo(app.OSCommand); isBare {
- if err != nil {
- return false, err
- }
+ if repoPaths.IsBareRepo() {
fmt.Print(app.Tr.BareRepo)
diff --git a/pkg/app/entry_point.go b/pkg/app/entry_point.go
index baeb43ae571..96f6176c78b 100644
--- a/pkg/app/entry_point.go
+++ b/pkg/app/entry_point.go
@@ -4,6 +4,8 @@ import (
"bytes"
"fmt"
"log"
+ "net/http"
+ _ "net/http/pprof"
"os"
"os/exec"
"path/filepath"
@@ -30,6 +32,7 @@ type cliArgs struct {
PrintVersionInfo bool
Debug bool
TailLogs bool
+ Profile bool
PrintDefaultConfig bool
PrintConfigDir bool
UseConfigDir string
@@ -145,6 +148,14 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
return
}
+ if cliArgs.Profile {
+ go func() {
+ if err := http.ListenAndServe("localhost:6060", nil); err != nil {
+ log.Fatal(err)
+ }
+ }()
+ }
+
parsedGitArg := parseGitArg(cliArgs.GitArg)
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest))
@@ -171,6 +182,9 @@ func parseCliArgsAndEnvVars() *cliArgs {
tailLogs := false
flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
+ profile := false
+ flaggy.Bool(&profile, "", "profile", "Start the profiler and serve it on http port 6060. See CONTRIBUTING.md for more info.")
+
printDefaultConfig := false
flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config")
@@ -202,6 +216,7 @@ func parseCliArgsAndEnvVars() *cliArgs {
PrintVersionInfo: printVersionInfo,
Debug: debug,
TailLogs: tailLogs,
+ Profile: profile,
PrintDefaultConfig: printDefaultConfig,
PrintConfigDir: printConfigDir,
UseConfigDir: useConfigDir,
diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go
index 27667e686f5..5f0d7549d5b 100644
--- a/pkg/cheatsheet/generate.go
+++ b/pkg/cheatsheet/generate.go
@@ -51,7 +51,10 @@ func GetKeybindingsDir() string {
}
func generateAtDir(cheatsheetDir string) {
- translationSetsByLang := i18n.GetTranslationSets()
+ translationSetsByLang, err := i18n.GetTranslationSets()
+ if err != nil {
+ log.Fatal(err)
+ }
mConfig := config.NewDummyAppConfig()
for lang := range translationSetsByLang {
diff --git a/pkg/cheatsheet/generate_test.go b/pkg/cheatsheet/generate_test.go
index 4d0d92e12a2..4dbf7e3dd72 100644
--- a/pkg/cheatsheet/generate_test.go
+++ b/pkg/cheatsheet/generate_test.go
@@ -262,7 +262,7 @@ func TestGetBindingSections(t *testing.T) {
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
- actual := getBindingSections(test.bindings, &tr)
+ actual := getBindingSections(test.bindings, tr)
assert.EqualValues(t, test.expected, actual)
})
}
diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go
index 8212b29b3d9..6c9aa874016 100644
--- a/pkg/commands/git_commands/branch.go
+++ b/pkg/commands/git_commands/branch.go
@@ -7,10 +7,12 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
+ "github.com/samber/lo"
)
type BranchCommands struct {
*GitCommon
+ allBranchesLogCmdIndex uint8 // keeps track of current all branches log command
}
func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
@@ -28,6 +30,15 @@ func (self *BranchCommands) New(name string, base string) error {
return self.cmd.New(cmdArgs).Run()
}
+func (self *BranchCommands) NewWithoutTracking(name string, base string) error {
+ cmdArgs := NewGitCmd("checkout").
+ Arg("-b", name, base).
+ Arg("--no-track").
+ ToArgv()
+
+ return self.cmd.New(cmdArgs).Run()
+}
+
// CreateWithUpstream creates a new branch with a given upstream, but without
// checking it out
func (self *BranchCommands) CreateWithUpstream(name string, upstream string) error {
@@ -216,13 +227,18 @@ func (self *BranchCommands) Rename(oldName string, newName string) error {
type MergeOpts struct {
FastForwardOnly bool
+ Squash bool
}
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
+ if opts.Squash && opts.FastForwardOnly {
+ panic("Squash and FastForwardOnly can't both be true")
+ }
cmdArgs := NewGitCmd("merge").
Arg("--no-edit").
Arg(strings.Fields(self.UserConfig.Git.Merging.Args)...).
ArgIf(opts.FastForwardOnly, "--ff-only").
+ ArgIf(opts.Squash, "--squash", "--ff").
Arg(branchName).
ToArgv()
@@ -230,5 +246,17 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
}
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
- return self.cmd.New(str.ToArgv(self.UserConfig.Git.AllBranchesLogCmd)).DontLog()
+ // Only choose between non-empty, non-identical commands
+ candidates := lo.Uniq(lo.WithoutEmpty(append([]string{
+ self.UserConfig.Git.AllBranchesLogCmd,
+ },
+ self.UserConfig.Git.AllBranchesLogCmds...,
+ )))
+
+ n := len(candidates)
+
+ i := self.allBranchesLogCmdIndex
+ self.allBranchesLogCmdIndex = uint8((int(i) + 1) % n)
+
+ return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
}
diff --git a/pkg/commands/git_commands/branch_loader.go b/pkg/commands/git_commands/branch_loader.go
index 16777243a86..929d5964d3f 100644
--- a/pkg/commands/git_commands/branch_loader.go
+++ b/pkg/commands/git_commands/branch_loader.go
@@ -5,6 +5,7 @@ import (
"regexp"
"strconv"
"strings"
+ "time"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/go-git/v5/config"
@@ -14,6 +15,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
+ "golang.org/x/sync/errgroup"
)
// context:
@@ -63,7 +65,13 @@ func NewBranchLoader(
}
// Load the list of branches for the current repo
-func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
+func (self *BranchLoader) Load(reflogCommits []*models.Commit,
+ mainBranches *MainBranches,
+ oldBranches []*models.Branch,
+ loadBehindCounts bool,
+ onWorker func(func() error),
+ renderFunc func(),
+) ([]*models.Branch, error) {
branches := self.obtainBranches(self.version.IsAtLeast(2, 22, 0))
if self.AppState.LocalBranchSortOrder == "recency" {
@@ -122,11 +130,108 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch
branch.UpstreamRemote = match.Remote
branch.UpstreamBranch = match.Merge.Short()
}
+
+ // If the branch already existed, take over its BehindBaseBranch value
+ // to reduce flicker
+ if oldBranch, found := lo.Find(oldBranches, func(b *models.Branch) bool {
+ return b.Name == branch.Name
+ }); found {
+ branch.BehindBaseBranch.Store(oldBranch.BehindBaseBranch.Load())
+ }
+ }
+
+ if loadBehindCounts && self.UserConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
+ onWorker(func() error {
+ return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc)
+ })
}
return branches, nil
}
+func (self *BranchLoader) GetBehindBaseBranchValuesForAllBranches(
+ branches []*models.Branch,
+ mainBranches *MainBranches,
+ renderFunc func(),
+) error {
+ mainBranchRefs := mainBranches.Get()
+ if len(mainBranchRefs) == 0 {
+ return nil
+ }
+
+ t := time.Now()
+ errg := errgroup.Group{}
+
+ for _, branch := range branches {
+ errg.Go(func() error {
+ baseBranch, err := self.GetBaseBranch(branch, mainBranches)
+ if err != nil {
+ return err
+ }
+ behind := 0 // prime it in case something below fails
+ if baseBranch != "" {
+ output, err := self.cmd.New(
+ NewGitCmd("rev-list").
+ Arg("--left-right").
+ Arg("--count").
+ Arg(fmt.Sprintf("%s...%s", branch.FullRefName(), baseBranch)).
+ ToArgv(),
+ ).DontLog().RunWithOutput()
+ if err != nil {
+ return err
+ }
+ // The format of the output is "\t"
+ aheadBehindStr := strings.Split(strings.TrimSpace(output), "\t")
+ if len(aheadBehindStr) == 2 {
+ if value, err := strconv.Atoi(aheadBehindStr[1]); err == nil {
+ behind = value
+ }
+ }
+ }
+ branch.BehindBaseBranch.Store(int32(behind))
+ return nil
+ })
+ }
+
+ err := errg.Wait()
+ self.Log.Debugf("time to get behind base branch values for all branches: %s", time.Since(t))
+ renderFunc()
+ return err
+}
+
+// Find the base branch for the given branch (i.e. the main branch that the
+// given branch was forked off of)
+//
+// Note that this function may return an empty string even if the returned error
+// is nil, e.g. when none of the configured main branches exist. This is not
+// considered an error condition, so callers need to check both the returned
+// error and whether the returned base branch is empty (and possibly react
+// differently in both cases).
+func (self *BranchLoader) GetBaseBranch(branch *models.Branch, mainBranches *MainBranches) (string, error) {
+ mergeBase := mainBranches.GetMergeBase(branch.FullRefName())
+ if mergeBase == "" {
+ return "", nil
+ }
+
+ output, err := self.cmd.New(
+ NewGitCmd("for-each-ref").
+ Arg("--contains").
+ Arg(mergeBase).
+ Arg("--format=%(refname)").
+ Arg(mainBranches.Get()...).
+ ToArgv(),
+ ).DontLog().RunWithOutput()
+ if err != nil {
+ return "", err
+ }
+ trimmedOutput := strings.TrimSpace(output)
+ split := strings.Split(trimmedOutput, "\n")
+ if len(split) == 0 || split[0] == "" {
+ return "", nil
+ }
+ return split[0], nil
+}
+
func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch {
output, err := self.getRawBranches()
if err != nil {
diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go
index 517be276e88..4153dfeb909 100644
--- a/pkg/commands/git_commands/commit.go
+++ b/pkg/commands/git_commands/commit.go
@@ -271,6 +271,7 @@ func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommand
Arg("-p").
Arg(hash).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
+ Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).
ArgIf(filterPath != "", "--", filterPath).
Dir(self.repoPaths.worktreePath).
ToArgv()
diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go
index 737e4c07731..f9cfff1419e 100644
--- a/pkg/commands/git_commands/commit_loader.go
+++ b/pkg/commands/git_commands/commit_loader.go
@@ -35,11 +35,6 @@ type CommitLoader struct {
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string
- // List of main branches that exist in the repo.
- // We use these to obtain the merge base of the branch.
- // When nil, we're yet to obtain the list of existing main branches.
- // When an empty slice, we've obtained the list and it's empty.
- mainBranches []string
*GitCommon
}
@@ -56,7 +51,6 @@ func NewCommitLoader(
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
- mainBranches: nil,
GitCommon: gitCommon,
}
}
@@ -72,6 +66,7 @@ type GetCommitsOptions struct {
All bool
// If non-empty, show divergence from this ref (left-right log)
RefToShowDivergenceFrom string
+ MainBranches *MainBranches
}
// GetCommits obtains the commits of the current branch
@@ -108,9 +103,9 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
go utils.Safe(func() {
defer wg.Done()
- ancestor = self.getMergeBase(opts.RefName)
+ ancestor = opts.MainBranches.GetMergeBase(opts.RefName)
if opts.RefToShowDivergenceFrom != "" {
- remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom)
+ remoteAncestor = opts.MainBranches.GetMergeBase(opts.RefToShowDivergenceFrom)
}
})
@@ -349,6 +344,8 @@ func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) []*mod
for _, t := range todos {
if t.Command == todo.UpdateRef {
t.Msg = t.Ref
+ } else if t.Command == todo.Exec {
+ t.Msg = t.ExecCommand
} else if t.Commit == "" {
// Command does not have a commit associated, skip
continue
@@ -471,82 +468,6 @@ func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {
}
}
-func (self *CommitLoader) getMergeBase(refName string) string {
- if self.mainBranches == nil {
- self.mainBranches = self.getExistingMainBranches()
- }
-
- if len(self.mainBranches) == 0 {
- return ""
- }
-
- // We pass all configured main branches to the merge-base call; git will
- // return the base commit for the closest one.
-
- output, err := self.cmd.New(
- NewGitCmd("merge-base").Arg(refName).Arg(self.mainBranches...).
- ToArgv(),
- ).DontLog().RunWithOutput()
- if err != nil {
- // If there's an error, it must be because one of the main branches that
- // used to exist when we called getExistingMainBranches() was deleted
- // meanwhile. To fix this for next time, throw away our cache.
- self.mainBranches = nil
- }
- return ignoringWarnings(output)
-}
-
-func (self *CommitLoader) getExistingMainBranches() []string {
- var existingBranches []string
- var wg sync.WaitGroup
-
- mainBranches := self.UserConfig.Git.MainBranches
- existingBranches = make([]string, len(mainBranches))
-
- for i, branchName := range mainBranches {
- wg.Add(1)
- go utils.Safe(func() {
- defer wg.Done()
-
- // Try to determine upstream of local main branch
- if ref, err := self.cmd.New(
- NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
- ).DontLog().RunWithOutput(); err == nil {
- existingBranches[i] = strings.TrimSpace(ref)
- return
- }
-
- // If this failed, a local branch for this main branch doesn't exist or it
- // has no upstream configured. Try looking for one in the "origin" remote.
- ref := "refs/remotes/origin/" + branchName
- if err := self.cmd.New(
- NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
- ).DontLog().Run(); err == nil {
- existingBranches[i] = ref
- return
- }
-
- // If this failed as well, try if we have the main branch as a local
- // branch. This covers the case where somebody is using git locally
- // for something, but never pushing anywhere.
- ref = "refs/heads/" + branchName
- if err := self.cmd.New(
- NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
- ).DontLog().Run(); err == nil {
- existingBranches[i] = ref
- }
- })
- }
-
- wg.Wait()
-
- existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
- return branch != ""
- })
-
- return existingBranches
-}
-
func ignoringWarnings(commandOutput string) string {
trimmedOutput := strings.TrimSpace(commandOutput)
split := strings.Split(trimmedOutput, "\n")
diff --git a/pkg/commands/git_commands/commit_loader_test.go b/pkg/commands/git_commands/commit_loader_test.go
index fe4f395855a..a8ef9e69a44 100644
--- a/pkg/commands/git_commands/commit_loader_test.go
+++ b/pkg/commands/git_commands/commit_loader_test.go
@@ -307,10 +307,11 @@ func TestGetCommits(t *testing.T) {
common := utils.NewDummyCommon()
common.AppState = &config.AppState{}
common.AppState.GitLogOrder = scenario.logOrder
+ cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner)
builder := &CommitLoader{
Common: common,
- cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
+ cmd: cmd,
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
@@ -322,7 +323,9 @@ func TestGetCommits(t *testing.T) {
}
common.UserConfig.Git.MainBranches = scenario.mainBranches
- commits, err := builder.GetCommits(scenario.opts)
+ opts := scenario.opts
+ opts.MainBranches = NewMainBranches(scenario.mainBranches, cmd)
+ commits, err := builder.GetCommits(opts)
assert.Equal(t, scenario.expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err)
diff --git a/pkg/commands/git_commands/commit_test.go b/pkg/commands/git_commands/commit_test.go
index c3708422ecb..239d7fa8f4c 100644
--- a/pkg/commands/git_commands/commit_test.go
+++ b/pkg/commands/git_commands/commit_test.go
@@ -228,54 +228,69 @@ func TestCommitCreateAmendCommit(t *testing.T) {
func TestCommitShowCmdObj(t *testing.T) {
type scenario struct {
- testName string
- filterPath string
- contextSize int
- ignoreWhitespace bool
- extDiffCmd string
- expected []string
+ testName string
+ filterPath string
+ contextSize int
+ similarityThreshold int
+ ignoreWhitespace bool
+ extDiffCmd string
+ expected []string
}
scenarios := []scenario{
{
- testName: "Default case without filter path",
- filterPath: "",
- contextSize: 3,
- ignoreWhitespace: false,
- extDiffCmd: "",
- expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
+ testName: "Default case without filter path",
+ filterPath: "",
+ contextSize: 3,
+ similarityThreshold: 50,
+ ignoreWhitespace: false,
+ extDiffCmd: "",
+ expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%"},
},
{
- testName: "Default case with filter path",
- filterPath: "file.txt",
- contextSize: 3,
- ignoreWhitespace: false,
- extDiffCmd: "",
- expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
+ testName: "Default case with filter path",
+ filterPath: "file.txt",
+ contextSize: 3,
+ similarityThreshold: 50,
+ ignoreWhitespace: false,
+ extDiffCmd: "",
+ expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--", "file.txt"},
},
{
- testName: "Show diff with custom context size",
- filterPath: "",
- contextSize: 77,
- ignoreWhitespace: false,
- extDiffCmd: "",
- expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
+ testName: "Show diff with custom context size",
+ filterPath: "",
+ contextSize: 77,
+ similarityThreshold: 50,
+ ignoreWhitespace: false,
+ extDiffCmd: "",
+ expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%"},
},
{
- testName: "Show diff, ignoring whitespace",
- filterPath: "",
- contextSize: 77,
- ignoreWhitespace: true,
- extDiffCmd: "",
- expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
+ testName: "Show diff with custom similarity threshold",
+ filterPath: "",
+ contextSize: 3,
+ similarityThreshold: 33,
+ ignoreWhitespace: false,
+ extDiffCmd: "",
+ expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=33%"},
},
{
- testName: "Show diff with external diff command",
- filterPath: "",
- contextSize: 3,
- ignoreWhitespace: false,
- extDiffCmd: "difft --color=always",
- expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
+ testName: "Show diff, ignoring whitespace",
+ filterPath: "",
+ contextSize: 77,
+ similarityThreshold: 50,
+ ignoreWhitespace: true,
+ extDiffCmd: "",
+ expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space", "--find-renames=50%"},
+ },
+ {
+ testName: "Show diff with external diff command",
+ filterPath: "",
+ contextSize: 3,
+ similarityThreshold: 50,
+ ignoreWhitespace: false,
+ extDiffCmd: "difft --color=always",
+ expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%"},
},
}
@@ -286,6 +301,7 @@ func TestCommitShowCmdObj(t *testing.T) {
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
+ appState.RenameSimilarityThreshold = s.similarityThreshold
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
repoPaths := RepoPaths{
diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go
index 73d7fdc6482..72329543a77 100644
--- a/pkg/commands/git_commands/file_loader.go
+++ b/pkg/commands/git_commands/file_loader.go
@@ -100,15 +100,19 @@ type FileStatus struct {
PreviousName string
}
-func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
+func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
cmdArgs := NewGitCmd("status").
Arg(opts.UntrackedFilesArg).
Arg("--porcelain").
Arg("-z").
- ArgIf(opts.NoRenames, "--no-renames").
+ ArgIfElse(
+ opts.NoRenames,
+ "--no-renames",
+ fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold),
+ ).
ToArgv()
- statusLines, _, err := c.cmd.New(cmdArgs).DontLog().RunWithOutputs()
+ statusLines, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs()
if err != nil {
return []FileStatus{}, err
}
diff --git a/pkg/commands/git_commands/file_loader_test.go b/pkg/commands/git_commands/file_loader_test.go
index 73fac7ef4e7..5a9f15700ed 100644
--- a/pkg/commands/git_commands/file_loader_test.go
+++ b/pkg/commands/git_commands/file_loader_test.go
@@ -5,27 +5,31 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
"github.com/stretchr/testify/assert"
)
func TestFileGetStatusFiles(t *testing.T) {
type scenario struct {
- testName string
- runner oscommands.ICmdObjRunner
- expectedFiles []*models.File
+ testName string
+ similarityThreshold int
+ runner oscommands.ICmdObjRunner
+ expectedFiles []*models.File
}
scenarios := []scenario{
{
"No files found",
+ 50,
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"}, "", nil),
+ ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "", nil),
[]*models.File{},
},
{
"Several files found",
+ 50,
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
+ ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
"MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
nil,
),
@@ -94,8 +98,9 @@ func TestFileGetStatusFiles(t *testing.T) {
},
{
"File with new line char",
+ 50,
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"}, "MM a\nb.txt", nil),
+ ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil),
[]*models.File{
{
Name: "a\nb.txt",
@@ -113,8 +118,9 @@ func TestFileGetStatusFiles(t *testing.T) {
},
{
"Renamed files",
+ 50,
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
+ ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
"R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
nil,
),
@@ -149,8 +155,9 @@ func TestFileGetStatusFiles(t *testing.T) {
},
{
"File with arrow in name",
+ 50,
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
+ ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
`?? a -> b.txt`,
nil,
),
@@ -175,8 +182,11 @@ func TestFileGetStatusFiles(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
+ appState := &config.AppState{}
+ appState.RenameSimilarityThreshold = s.similarityThreshold
+
loader := &FileLoader{
- GitCommon: buildGitCommon(commonDeps{}),
+ GitCommon: buildGitCommon(commonDeps{appState: appState}),
cmd: cmd,
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
getFileType: func(string) string { return "file" },
diff --git a/pkg/commands/git_commands/main_branches.go b/pkg/commands/git_commands/main_branches.go
new file mode 100644
index 00000000000..341232b04a2
--- /dev/null
+++ b/pkg/commands/git_commands/main_branches.go
@@ -0,0 +1,122 @@
+package git_commands
+
+import (
+ "strings"
+ "sync"
+
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/samber/lo"
+ "github.com/sasha-s/go-deadlock"
+)
+
+type MainBranches struct {
+ // List of main branches configured by the user. Just the bare names.
+ configuredMainBranches []string
+ // Which of these actually exist in the repository. Full ref names, and it
+ // could be either "refs/heads/..." or "refs/remotes/origin/..." depending
+ // on which one exists for a given bare name.
+ existingMainBranches []string
+
+ cmd oscommands.ICmdObjBuilder
+ mutex *deadlock.Mutex
+}
+
+func NewMainBranches(
+ configuredMainBranches []string,
+ cmd oscommands.ICmdObjBuilder,
+) *MainBranches {
+ return &MainBranches{
+ configuredMainBranches: configuredMainBranches,
+ existingMainBranches: nil,
+ cmd: cmd,
+ mutex: &deadlock.Mutex{},
+ }
+}
+
+// Get the list of main branches that exist in the repository. This is a list of
+// full ref names.
+func (self *MainBranches) Get() []string {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ if self.existingMainBranches == nil {
+ self.existingMainBranches = self.determineMainBranches()
+ }
+
+ return self.existingMainBranches
+}
+
+// Return the merge base of the given refName with the closest main branch.
+func (self *MainBranches) GetMergeBase(refName string) string {
+ mainBranches := self.Get()
+ if len(mainBranches) == 0 {
+ return ""
+ }
+
+ // We pass all existing main branches to the merge-base call; git will
+ // return the base commit for the closest one.
+
+ // We ignore errors from this call, since we can't distinguish whether the
+ // error is because one of the main branches has been deleted since the last
+ // call to determineMainBranches, or because the refName has no common
+ // history with any of the main branches. Since the former should happen
+ // very rarely, users must quit and restart lazygit to fix it; the latter is
+ // also not very common, but can totally happen and is not an error.
+
+ output, _ := self.cmd.New(
+ NewGitCmd("merge-base").Arg(refName).Arg(mainBranches...).
+ ToArgv(),
+ ).DontLog().RunWithOutput()
+ return ignoringWarnings(output)
+}
+
+func (self *MainBranches) determineMainBranches() []string {
+ var existingBranches []string
+ var wg sync.WaitGroup
+
+ existingBranches = make([]string, len(self.configuredMainBranches))
+
+ for i, branchName := range self.configuredMainBranches {
+ wg.Add(1)
+ go utils.Safe(func() {
+ defer wg.Done()
+
+ // Try to determine upstream of local main branch
+ if ref, err := self.cmd.New(
+ NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
+ ).DontLog().RunWithOutput(); err == nil {
+ existingBranches[i] = strings.TrimSpace(ref)
+ return
+ }
+
+ // If this failed, a local branch for this main branch doesn't exist or it
+ // has no upstream configured. Try looking for one in the "origin" remote.
+ ref := "refs/remotes/origin/" + branchName
+ if err := self.cmd.New(
+ NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
+ ).DontLog().Run(); err == nil {
+ existingBranches[i] = ref
+ return
+ }
+
+ // If this failed as well, try if we have the main branch as a local
+ // branch. This covers the case where somebody is using git locally
+ // for something, but never pushing anywhere.
+ ref = "refs/heads/" + branchName
+ if err := self.cmd.New(
+ NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
+ ).DontLog().Run(); err == nil {
+ existingBranches[i] = ref
+ }
+ })
+ }
+
+ wg.Wait()
+
+ existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
+ return branch != ""
+ })
+
+ return existingBranches
+}
diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go
index 3d18bf3e2e7..bceaf994365 100644
--- a/pkg/commands/git_commands/patch.go
+++ b/pkg/commands/git_commands/patch.go
@@ -47,8 +47,8 @@ type ApplyPatchOpts struct {
Reverse bool
}
-func (self *PatchCommands) ApplyCustomPatch(reverse bool) error {
- patch := self.PatchBuilder.PatchToApply(reverse)
+func (self *PatchCommands) ApplyCustomPatch(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) error {
+ patch := self.PatchBuilder.PatchToApply(reverse, turnAddedFilesIntoDiffAgainstEmptyFile)
return self.ApplyPatch(patch, ApplyPatchOpts{
Index: true,
@@ -94,7 +94,7 @@ func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, com
}
// apply each patch in reverse
- if err := self.ApplyCustomPatch(true); err != nil {
+ if err := self.ApplyCustomPatch(true, true); err != nil {
_ = self.rebase.AbortRebase()
return err
}
@@ -123,7 +123,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
}
// apply each patch forward
- if err := self.ApplyCustomPatch(false); err != nil {
+ if err := self.ApplyCustomPatch(false, false); err != nil {
// Don't abort the rebase here; this might cause conflicts, so give
// the user a chance to resolve them
return err
@@ -172,7 +172,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
}
// apply each patch in reverse
- if err := self.ApplyCustomPatch(true); err != nil {
+ if err := self.ApplyCustomPatch(true, true); err != nil {
_ = self.rebase.AbortRebase()
return err
}
@@ -228,7 +228,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
return err
}
- if err := self.ApplyCustomPatch(true); err != nil {
+ if err := self.ApplyCustomPatch(true, true); err != nil {
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
_ = self.rebase.AbortRebase()
}
@@ -282,7 +282,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(
return err
}
- if err := self.ApplyCustomPatch(true); err != nil {
+ if err := self.ApplyCustomPatch(true, true); err != nil {
_ = self.rebase.AbortRebase()
return err
}
diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go
index 040df0070c8..59c2c6833c0 100644
--- a/pkg/commands/git_commands/rebase.go
+++ b/pkg/commands/git_commands/rebase.go
@@ -67,42 +67,47 @@ func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index
}), nil
}
-func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
- return self.GenericAmend(commits, index, func() error {
+func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, start, end int) error {
+ return self.GenericAmend(commits, start, end, func(_ *models.Commit) error {
return self.commit.ResetAuthor()
})
}
-func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int, value string) error {
- return self.GenericAmend(commits, index, func() error {
+func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, start, end int, value string) error {
+ return self.GenericAmend(commits, start, end, func(_ *models.Commit) error {
return self.commit.SetAuthor(value)
})
}
-func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, index int, value string) error {
- return self.GenericAmend(commits, index, func() error {
- return self.commit.AddCoAuthor(commits[index].Hash, value)
+func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, start, end int, value string) error {
+ return self.GenericAmend(commits, start, end, func(commit *models.Commit) error {
+ return self.commit.AddCoAuthor(commit.Hash, value)
})
}
-func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
- if models.IsHeadCommit(commits, index) {
+func (self *RebaseCommands) GenericAmend(commits []*models.Commit, start, end int, f func(commit *models.Commit) error) error {
+ if start == end && models.IsHeadCommit(commits, start) {
// we've selected the top commit so no rebase is required
- return f()
+ return f(commits[start])
}
- err := self.BeginInteractiveRebaseForCommit(commits, index, false)
+ err := self.BeginInteractiveRebaseForCommitRange(commits, start, end, false)
if err != nil {
return err
}
- // now the selected commit should be our head so we'll amend it
- err = f()
- if err != nil {
- return err
+ for commitIndex := end; commitIndex >= start; commitIndex-- {
+ err = f(commits[commitIndex])
+ if err != nil {
+ return err
+ }
+
+ if err := self.ContinueRebase(); err != nil {
+ return err
+ }
}
- return self.ContinueRebase()
+ return nil
}
func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error {
@@ -381,7 +386,13 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
func (self *RebaseCommands) BeginInteractiveRebaseForCommit(
commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool,
) error {
- if len(commits)-1 < commitIndex {
+ return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty)
+}
+
+func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
+ commits []*models.Commit, start, end int, keepCommitsThatBecomeEmpty bool,
+) error {
+ if len(commits)-1 < end {
return errors.New("index outside of range of commits")
}
@@ -392,14 +403,17 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(
return errors.New(self.Tr.DisabledForGPG)
}
- changes := []daemon.ChangeTodoAction{{
- Hash: commits[commitIndex].Hash,
- NewAction: todo.Edit,
- }}
+ changes := make([]daemon.ChangeTodoAction, 0, end-start)
+ for commitIndex := end; commitIndex >= start; commitIndex-- {
+ changes = append(changes, daemon.ChangeTodoAction{
+ Hash: commits[commitIndex].Hash,
+ NewAction: todo.Edit,
+ })
+ }
self.os.LogCommand(logTodoChanges(changes), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
- baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
+ baseHashOrRoot: getBaseHashOrRoot(commits, end+1),
overrideEditor: true,
keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty,
instruction: daemon.NewChangeTodoActionsInstruction(changes),
diff --git a/pkg/commands/git_commands/repo_paths.go b/pkg/commands/git_commands/repo_paths.go
index b0e1970dbe8..c2e77d4464a 100644
--- a/pkg/commands/git_commands/repo_paths.go
+++ b/pkg/commands/git_commands/repo_paths.go
@@ -2,6 +2,7 @@ package git_commands
import (
ioFs "io/fs"
+ "os"
"path"
"path/filepath"
"strings"
@@ -18,6 +19,7 @@ type RepoPaths struct {
repoPath string
repoGitDirPath string
repoName string
+ isBareRepo bool
}
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
@@ -54,6 +56,10 @@ func (self *RepoPaths) RepoName() string {
return self.repoName
}
+func (self *RepoPaths) IsBareRepo() bool {
+ return self.isBareRepo
+}
+
// Returns the repo paths for a typical repo
func MockRepoPaths(currentPath string) *RepoPaths {
return &RepoPaths{
@@ -62,6 +68,7 @@ func MockRepoPaths(currentPath string) *RepoPaths {
repoPath: currentPath,
repoGitDirPath: path.Join(currentPath, ".git"),
repoName: "lazygit",
+ isBareRepo: false,
}
}
@@ -69,7 +76,19 @@ func GetRepoPaths(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
) (*RepoPaths, error) {
- gitDirOutput, err := callGitRevParse(cmd, version, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree")
+ cwd, err := os.Getwd()
+ if err != nil {
+ return nil, err
+ }
+ return GetRepoPathsForDir(cwd, cmd, version)
+}
+
+func GetRepoPathsForDir(
+ dir string,
+ cmd oscommands.ICmdObjBuilder,
+ version *GitVersion,
+) (*RepoPaths, error) {
+ gitDirOutput, err := callGitRevParseWithDir(cmd, version, dir, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree")
if err != nil {
return nil, err
}
@@ -84,13 +103,14 @@ func GetRepoPaths(
return nil, err
}
}
+ isBareRepo := gitDirResults[3] == "true"
// If we're in a submodule, --show-superproject-working-tree will return
- // a value, meaning gitDirResults will be length 4. In that case
+ // a value, meaning gitDirResults will be length 5. In that case
// return the worktree path as the repoPath. Otherwise we're in a
// normal repo or a worktree so return the parent of the git common
// dir (repoGitDirPath)
- isSubmodule := len(gitDirResults) == 4
+ isSubmodule := len(gitDirResults) == 5
var repoPath string
if isSubmodule {
@@ -106,17 +126,10 @@ func GetRepoPaths(
repoPath: repoPath,
repoGitDirPath: repoGitDirPath,
repoName: repoName,
+ isBareRepo: isBareRepo,
}, nil
}
-func callGitRevParse(
- cmd oscommands.ICmdObjBuilder,
- version *GitVersion,
- gitRevArgs ...string,
-) (string, error) {
- return callGitRevParseWithDir(cmd, version, "", gitRevArgs...)
-}
-
func callGitRevParseWithDir(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
diff --git a/pkg/commands/git_commands/repo_paths_test.go b/pkg/commands/git_commands/repo_paths_test.go
index 97cfc811908..9ee41a3fc8e 100644
--- a/pkg/commands/git_commands/repo_paths_test.go
+++ b/pkg/commands/git_commands/repo_paths_test.go
@@ -36,10 +36,12 @@ func TestGetRepoPaths(t *testing.T) {
"/path/to/repo/.git",
// --git-common-dir
"/path/to/repo/.git",
+ // --is-bare-repository
+ "false",
// --show-superproject-working-tree
}
runner.ExpectGitArgs(
- append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
+ append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
nil)
},
@@ -50,6 +52,38 @@ func TestGetRepoPaths(t *testing.T) {
repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
repoName: "repo",
+ isBareRepo: false,
+ },
+ Err: nil,
+ },
+ {
+ Name: "bare repo",
+ BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
+ // setup for main worktree
+ expectedOutput := []string{
+ // --show-toplevel
+ "/path/to/repo",
+ // --git-dir
+ "/path/to/bare_repo/bare.git",
+ // --git-common-dir
+ "/path/to/bare_repo/bare.git",
+ // --is-bare-repository
+ "true",
+ // --show-superproject-working-tree
+ }
+ runner.ExpectGitArgs(
+ append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
+ strings.Join(expectedOutput, "\n"),
+ nil)
+ },
+ Path: "/path/to/repo",
+ Expected: &RepoPaths{
+ worktreePath: "/path/to/repo",
+ worktreeGitDirPath: "/path/to/bare_repo/bare.git",
+ repoPath: "/path/to/bare_repo",
+ repoGitDirPath: "/path/to/bare_repo/bare.git",
+ repoName: "bare_repo",
+ isBareRepo: true,
},
Err: nil,
},
@@ -63,11 +97,13 @@ func TestGetRepoPaths(t *testing.T) {
"/path/to/repo/.git/modules/submodule1",
// --git-common-dir
"/path/to/repo/.git/modules/submodule1",
+ // --is-bare-repository
+ "false",
// --show-superproject-working-tree
"/path/to/repo",
}
runner.ExpectGitArgs(
- append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
+ append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
nil)
},
@@ -78,6 +114,7 @@ func TestGetRepoPaths(t *testing.T) {
repoPath: "/path/to/repo/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoName: "submodule1",
+ isBareRepo: false,
},
Err: nil,
},
@@ -85,7 +122,7 @@ func TestGetRepoPaths(t *testing.T) {
Name: "git rev-parse returns an error",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
runner.ExpectGitArgs(
- append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
+ append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
"",
errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git"))
},
@@ -94,7 +131,7 @@ func TestGetRepoPaths(t *testing.T) {
Err: func(getRevParseArgs argFn) error {
args := strings.Join(getRevParseArgs(), " ")
return errors.New(
- fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
+ fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
)
},
},
@@ -120,7 +157,7 @@ func TestGetRepoPaths(t *testing.T) {
// prepare the filesystem for the scenario
s.BeforeFunc(runner, getRevParseArgs)
- repoPaths, err := GetRepoPaths(cmd, version)
+ repoPaths, err := GetRepoPathsForDir("", cmd, version)
// check the error and the paths
if s.Err != nil {
diff --git a/pkg/commands/git_commands/stash.go b/pkg/commands/git_commands/stash.go
index 5eeaa6a68c9..047985e38ed 100644
--- a/pkg/commands/git_commands/stash.go
+++ b/pkg/commands/git_commands/stash.go
@@ -87,6 +87,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
+ Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).
Arg(fmt.Sprintf("stash@{%d}", index)).
Dir(self.repoPaths.worktreePath).
ToArgv()
diff --git a/pkg/commands/git_commands/stash_test.go b/pkg/commands/git_commands/stash_test.go
index accd0589075..207ddb1265f 100644
--- a/pkg/commands/git_commands/stash_test.go
+++ b/pkg/commands/git_commands/stash_test.go
@@ -98,34 +98,46 @@ func TestStashHash(t *testing.T) {
func TestStashStashEntryCmdObj(t *testing.T) {
type scenario struct {
- testName string
- index int
- contextSize int
- ignoreWhitespace bool
- expected []string
+ testName string
+ index int
+ contextSize int
+ similarityThreshold int
+ ignoreWhitespace bool
+ expected []string
}
scenarios := []scenario{
{
- testName: "Default case",
- index: 5,
- contextSize: 3,
- ignoreWhitespace: false,
- expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "stash@{5}"},
+ testName: "Default case",
+ index: 5,
+ contextSize: 3,
+ similarityThreshold: 50,
+ ignoreWhitespace: false,
+ expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--find-renames=50%", "stash@{5}"},
},
{
- testName: "Show diff with custom context size",
- index: 5,
- contextSize: 77,
- ignoreWhitespace: false,
- expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=77", "stash@{5}"},
+ testName: "Show diff with custom context size",
+ index: 5,
+ contextSize: 77,
+ similarityThreshold: 50,
+ ignoreWhitespace: false,
+ expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=77", "--find-renames=50%", "stash@{5}"},
},
{
- testName: "Default case",
- index: 5,
- contextSize: 3,
- ignoreWhitespace: true,
- expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--ignore-all-space", "stash@{5}"},
+ testName: "Show diff with custom similarity threshold",
+ index: 5,
+ contextSize: 3,
+ similarityThreshold: 33,
+ ignoreWhitespace: false,
+ expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--find-renames=33%", "stash@{5}"},
+ },
+ {
+ testName: "Default case",
+ index: 5,
+ contextSize: 3,
+ similarityThreshold: 50,
+ ignoreWhitespace: true,
+ expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "stash@{5}"},
},
}
@@ -135,6 +147,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
+ appState.RenameSimilarityThreshold = s.similarityThreshold
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
diff --git a/pkg/commands/git_commands/status.go b/pkg/commands/git_commands/status.go
index 65b29deef13..0e0ef37fcc5 100644
--- a/pkg/commands/git_commands/status.go
+++ b/pkg/commands/git_commands/status.go
@@ -3,10 +3,8 @@ package git_commands
import (
"os"
"path/filepath"
- "strconv"
"strings"
- "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
)
@@ -49,20 +47,8 @@ func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
return enums.REBASE_MODE_NONE
}
-func (self *StatusCommands) IsBareRepo() (bool, error) {
- return IsBareRepo(self.os)
-}
-
-func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
- res, err := osCommand.Cmd.New(
- NewGitCmd("rev-parse").Arg("--is-bare-repository").ToArgv(),
- ).DontLog().RunWithOutput()
- if err != nil {
- return false, err
- }
-
- // The command returns output with a newline, so we need to strip
- return strconv.ParseBool(strings.TrimSpace(res))
+func (self *StatusCommands) IsBareRepo() bool {
+ return self.repoPaths.isBareRepo
}
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go
index 4ab1f336bf0..7ab209c0ead 100644
--- a/pkg/commands/git_commands/sync.go
+++ b/pkg/commands/git_commands/sync.go
@@ -19,6 +19,7 @@ func NewSyncCommands(gitCommon *GitCommon) *SyncCommands {
// Push pushes to a branch
type PushOpts struct {
Force bool
+ ForceWithLease bool
UpstreamRemote string
UpstreamBranch string
SetUpstream bool
@@ -30,10 +31,11 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
}
cmdArgs := NewGitCmd("push").
- ArgIf(opts.Force, "--force-with-lease").
+ ArgIf(opts.Force, "--force").
+ ArgIf(opts.ForceWithLease, "--force-with-lease").
ArgIf(opts.SetUpstream, "--set-upstream").
ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
- ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
+ ArgIf(opts.UpstreamBranch != "", "HEAD:"+opts.UpstreamBranch).
ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
diff --git a/pkg/commands/git_commands/sync_test.go b/pkg/commands/git_commands/sync_test.go
index f5f281e147d..353ac72aa22 100644
--- a/pkg/commands/git_commands/sync_test.go
+++ b/pkg/commands/git_commands/sync_test.go
@@ -18,62 +18,70 @@ func TestSyncPush(t *testing.T) {
scenarios := []scenario{
{
testName: "Push with force disabled",
- opts: PushOpts{Force: false},
+ opts: PushOpts{ForceWithLease: false},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push"})
assert.NoError(t, err)
},
},
+ {
+ testName: "Push with force-with-lease enabled",
+ opts: PushOpts{ForceWithLease: true},
+ test: func(cmdObj oscommands.ICmdObj, err error) {
+ assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
+ assert.NoError(t, err)
+ },
+ },
{
testName: "Push with force enabled",
opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
- assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
+ assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force"})
assert.NoError(t, err)
},
},
{
testName: "Push with force disabled, upstream supplied",
opts: PushOpts{
- Force: false,
+ ForceWithLease: false,
UpstreamRemote: "origin",
UpstreamBranch: "master",
},
test: func(cmdObj oscommands.ICmdObj, err error) {
- assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "master"})
+ assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "HEAD:master"})
assert.NoError(t, err)
},
},
{
testName: "Push with force disabled, setting upstream",
opts: PushOpts{
- Force: false,
+ ForceWithLease: false,
UpstreamRemote: "origin",
UpstreamBranch: "master",
SetUpstream: true,
},
test: func(cmdObj oscommands.ICmdObj, err error) {
- assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "master"})
+ assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "HEAD:master"})
assert.NoError(t, err)
},
},
{
- testName: "Push with force enabled, setting upstream",
+ testName: "Push with force-with-lease enabled, setting upstream",
opts: PushOpts{
- Force: true,
+ ForceWithLease: true,
UpstreamRemote: "origin",
UpstreamBranch: "master",
SetUpstream: true,
},
test: func(cmdObj oscommands.ICmdObj, err error) {
- assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "master"})
+ assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "HEAD:master"})
assert.NoError(t, err)
},
},
{
testName: "Push with remote branch but no origin",
opts: PushOpts{
- Force: true,
+ ForceWithLease: true,
UpstreamRemote: "",
UpstreamBranch: "master",
SetUpstream: true,
diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go
index 7639dbad8ca..2364f2a68e5 100644
--- a/pkg/commands/git_commands/working_tree.go
+++ b/pkg/commands/git_commands/working_tree.go
@@ -263,6 +263,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(fmt.Sprintf("--color=%s", colorArg)).
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
+ Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).
ArgIf(cached, "--cached").
ArgIf(noIndex, "--no-index").
Arg("--").
diff --git a/pkg/commands/git_commands/working_tree_test.go b/pkg/commands/git_commands/working_tree_test.go
index cc0ad55f5f3..a4270e732c5 100644
--- a/pkg/commands/git_commands/working_tree_test.go
+++ b/pkg/commands/git_commands/working_tree_test.go
@@ -205,13 +205,14 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
func TestWorkingTreeDiff(t *testing.T) {
type scenario struct {
- testName string
- file *models.File
- plain bool
- cached bool
- ignoreWhitespace bool
- contextSize int
- runner *oscommands.FakeCmdObjRunner
+ testName string
+ file *models.File
+ plain bool
+ cached bool
+ ignoreWhitespace bool
+ contextSize int
+ similarityThreshold int
+ runner *oscommands.FakeCmdObjRunner
}
const expectedResult = "pretend this is an actual git diff"
@@ -224,12 +225,13 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
- plain: false,
- cached: false,
- ignoreWhitespace: false,
- contextSize: 3,
+ plain: false,
+ cached: false,
+ ignoreWhitespace: false,
+ contextSize: 3,
+ similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "cached",
@@ -238,12 +240,13 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
- plain: false,
- cached: true,
- ignoreWhitespace: false,
- contextSize: 3,
+ plain: false,
+ cached: true,
+ ignoreWhitespace: false,
+ contextSize: 3,
+ similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--cached", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "plain",
@@ -252,12 +255,13 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
- plain: true,
- cached: false,
- ignoreWhitespace: false,
- contextSize: 3,
+ plain: true,
+ cached: false,
+ ignoreWhitespace: false,
+ contextSize: 3,
+ similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "File not tracked and file has no staged changes",
@@ -266,12 +270,13 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: false,
},
- plain: false,
- cached: false,
- ignoreWhitespace: false,
- contextSize: 3,
+ plain: false,
+ cached: false,
+ ignoreWhitespace: false,
+ contextSize: 3,
+ similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
@@ -280,12 +285,13 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
- plain: false,
- cached: false,
- ignoreWhitespace: true,
- contextSize: 3,
+ plain: false,
+ cached: false,
+ ignoreWhitespace: true,
+ contextSize: 3,
+ similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
@@ -294,12 +300,28 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
- plain: false,
- cached: false,
- ignoreWhitespace: false,
- contextSize: 17,
+ plain: false,
+ cached: false,
+ ignoreWhitespace: false,
+ contextSize: 17,
+ similarityThreshold: 50,
+ runner: oscommands.NewFakeRunner(t).
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil),
+ },
+ {
+ testName: "Show diff with custom similarity threshold",
+ file: &models.File{
+ Name: "test.txt",
+ HasStagedChanges: false,
+ Tracked: true,
+ },
+ plain: false,
+ cached: false,
+ ignoreWhitespace: false,
+ contextSize: 3,
+ similarityThreshold: 33,
runner: oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
+ ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=33%", "--", "test.txt"}, expectedResult, nil),
},
}
@@ -309,6 +331,7 @@ func TestWorkingTreeDiff(t *testing.T) {
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
+ appState.RenameSimilarityThreshold = s.similarityThreshold
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
diff --git a/pkg/commands/hosting_service/hosting_service_test.go b/pkg/commands/hosting_service/hosting_service_test.go
index 20632e983d8..4ce847bf76c 100644
--- a/pkg/commands/hosting_service/hosting_service_test.go
+++ b/pkg/commands/hosting_service/hosting_service_test.go
@@ -416,7 +416,7 @@ func TestGetPullRequestURL(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
tr := i18n.EnglishTranslationSet()
log := &fakes.FakeFieldLogger{}
- hostingServiceMgr := NewHostingServiceMgr(log, &tr, s.remoteUrl, s.configServiceDomains)
+ hostingServiceMgr := NewHostingServiceMgr(log, tr, s.remoteUrl, s.configServiceDomains)
s.test(hostingServiceMgr.GetPullRequestURL(s.from, s.to))
log.AssertErrors(t, s.expectedLoggedErrors)
})
diff --git a/pkg/commands/models/branch.go b/pkg/commands/models/branch.go
index 25d806fca0e..04f869ebdbd 100644
--- a/pkg/commands/models/branch.go
+++ b/pkg/commands/models/branch.go
@@ -1,6 +1,9 @@
package models
-import "fmt"
+import (
+ "fmt"
+ "sync/atomic"
+)
// Branch : A git branch
// duplicating this for now
@@ -32,6 +35,11 @@ type Branch struct {
Subject string
// commit hash
CommitHash string
+
+ // How far we have fallen behind our base branch. 0 means either not
+ // determined yet, or up to date with base branch. (We don't need to
+ // distinguish the two, as we don't draw anything in both cases.)
+ BehindBaseBranch atomic.Int32
}
func (b *Branch) FullRefName() string {
@@ -104,7 +112,7 @@ func (b *Branch) IsBehindForPull() bool {
}
func (b *Branch) IsBehindForPush() bool {
- return b.BehindForPush != "" && b.BehindForPush != "0"
+ return b.RemoteBranchStoredLocally() && b.BehindForPush != "0"
}
// for when we're in a detached head state
diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go
index 16257158e18..1dd6ab2b277 100644
--- a/pkg/commands/oscommands/cmd_obj_runner.go
+++ b/pkg/commands/oscommands/cmd_obj_runner.go
@@ -284,6 +284,7 @@ const (
Username
Passphrase
PIN
+ Token
)
// Whenever we're asked for a password we just enter a newline, which will
@@ -376,6 +377,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
`Username\s*for\s*'.+':`: Username,
`Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
`Enter\s*PIN\s*for\s*.+\s*key\s*.+:`: PIN,
+ `.*2FA Token.*`: Token,
}
compiledPrompts := map[*regexp.Regexp]CredentialType{}
diff --git a/pkg/commands/oscommands/cmd_obj_runner_test.go b/pkg/commands/oscommands/cmd_obj_runner_test.go
index 31966cec1c9..c906cea3fa3 100644
--- a/pkg/commands/oscommands/cmd_obj_runner_test.go
+++ b/pkg/commands/oscommands/cmd_obj_runner_test.go
@@ -39,6 +39,8 @@ func TestProcessOutput(t *testing.T) {
return "passphrase"
case PIN:
return "pin"
+ case Token:
+ return "token"
default:
panic("unexpected credential type")
}
@@ -92,6 +94,12 @@ func TestProcessOutput(t *testing.T) {
output: "Enter PIN for key '123':",
expectedToWrite: "pin",
},
+ {
+ name: "2FA token prompt",
+ promptUserForCredential: defaultPromptUserForCredential,
+ output: "testuser 2FA Token (citadel)",
+ expectedToWrite: "token",
+ },
{
name: "username and password prompt",
promptUserForCredential: defaultPromptUserForCredential,
diff --git a/pkg/commands/oscommands/os.go b/pkg/commands/oscommands/os.go
index 0a6bf7397c9..7771dffba5f 100644
--- a/pkg/commands/oscommands/os.go
+++ b/pkg/commands/oscommands/os.go
@@ -302,6 +302,23 @@ func (c *OSCommand) CopyToClipboard(str string) error {
return clipboard.WriteAll(str)
}
+func (c *OSCommand) PasteFromClipboard() (string, error) {
+ var s string
+ var err error
+ if c.UserConfig.OS.CopyToClipboardCmd != "" {
+ cmdStr := c.UserConfig.OS.ReadFromClipboardCmd
+ s, err = c.Cmd.NewShell(cmdStr).RunWithOutput()
+ } else {
+ s, err = clipboard.ReadAll()
+ }
+
+ if err != nil {
+ return "", err
+ }
+
+ return strings.ReplaceAll(s, "\r\n", "\n"), nil
+}
+
func (c *OSCommand) RemoveFile(path string) error {
msg := utils.ResolvePlaceholderString(
c.Tr.Log.RemoveFile,
diff --git a/pkg/commands/patch/patch_builder.go b/pkg/commands/patch/patch_builder.go
index a1e2f5194ef..5ef81c72e47 100644
--- a/pkg/commands/patch/patch_builder.go
+++ b/pkg/commands/patch/patch_builder.go
@@ -65,7 +65,7 @@ func (p *PatchBuilder) Start(from, to string, reverse bool, canRebase bool) {
p.fileInfoMap = map[string]*fileInfo{}
}
-func (p *PatchBuilder) PatchToApply(reverse bool) string {
+func (p *PatchBuilder) PatchToApply(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) string {
patch := ""
for filename, info := range p.fileInfoMap {
@@ -73,7 +73,12 @@ func (p *PatchBuilder) PatchToApply(reverse bool) string {
continue
}
- patch += p.RenderPatchForFile(filename, true, reverse)
+ patch += p.RenderPatchForFile(RenderPatchForFileOpts{
+ Filename: filename,
+ Plain: true,
+ Reverse: reverse,
+ TurnAddedFilesIntoDiffAgainstEmptyFile: turnAddedFilesIntoDiffAgainstEmptyFile,
+ })
}
return patch
@@ -172,8 +177,15 @@ func (p *PatchBuilder) RemoveFileLineRange(filename string, firstLineIdx, lastLi
return nil
}
-func (p *PatchBuilder) RenderPatchForFile(filename string, plain bool, reverse bool) string {
- info, err := p.getFileInfo(filename)
+type RenderPatchForFileOpts struct {
+ Filename string
+ Plain bool
+ Reverse bool
+ TurnAddedFilesIntoDiffAgainstEmptyFile bool
+}
+
+func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
+ info, err := p.getFileInfo(opts.Filename)
if err != nil {
p.Log.Error(err)
return ""
@@ -183,7 +195,7 @@ func (p *PatchBuilder) RenderPatchForFile(filename string, plain bool, reverse b
return ""
}
- if info.mode == WHOLE && plain {
+ if info.mode == WHOLE && opts.Plain {
// Use the whole diff (spares us parsing it and then formatting it).
// TODO: see if this is actually noticeably faster.
// The reverse flag is only for part patches so we're ignoring it here.
@@ -192,11 +204,12 @@ func (p *PatchBuilder) RenderPatchForFile(filename string, plain bool, reverse b
patch := Parse(info.diff).
Transform(TransformOpts{
- Reverse: reverse,
- IncludedLineIndices: info.includedLineIndices,
+ Reverse: opts.Reverse,
+ TurnAddedFilesIntoDiffAgainstEmptyFile: opts.TurnAddedFilesIntoDiffAgainstEmptyFile,
+ IncludedLineIndices: info.includedLineIndices,
})
- if plain {
+ if opts.Plain {
return patch.FormatPlain()
} else {
return patch.FormatView(FormatViewOpts{})
@@ -209,7 +222,12 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
sort.Strings(filenames)
patches := lo.Map(filenames, func(filename string, _ int) string {
- return p.RenderPatchForFile(filename, plain, false)
+ return p.RenderPatchForFile(RenderPatchForFileOpts{
+ Filename: filename,
+ Plain: plain,
+ Reverse: false,
+ TurnAddedFilesIntoDiffAgainstEmptyFile: true,
+ })
})
output := lo.Filter(patches, func(patch string, _ int) bool {
return patch != ""
diff --git a/pkg/commands/patch/transform.go b/pkg/commands/patch/transform.go
index f861a6540d9..db35bb4a11c 100644
--- a/pkg/commands/patch/transform.go
+++ b/pkg/commands/patch/transform.go
@@ -1,6 +1,10 @@
package patch
-import "github.com/samber/lo"
+import (
+ "strings"
+
+ "github.com/samber/lo"
+)
type patchTransformer struct {
patch *Patch
@@ -22,6 +26,13 @@ type TransformOpts struct {
// information it needs to cleanly apply patches
FileNameOverride string
+ // Custom patches tend to work better when treating new files as diffs
+ // against an empty file. The only case where we need this to be false is
+ // when moving a custom patch to an earlier commit; in that case the patch
+ // command would fail with the error "file does not exist in index" if we
+ // treat it as a diff against an empty file.
+ TurnAddedFilesIntoDiffAgainstEmptyFile bool
+
// The indices of lines that should be included in the patch.
IncludedLineIndices []int
}
@@ -61,6 +72,18 @@ func (self *patchTransformer) transformHeader() []string {
"--- a/" + self.opts.FileNameOverride,
"+++ b/" + self.opts.FileNameOverride,
}
+ } else if self.opts.TurnAddedFilesIntoDiffAgainstEmptyFile {
+ result := make([]string, 0, len(self.patch.header))
+ for idx, line := range self.patch.header {
+ if strings.HasPrefix(line, "new file mode") {
+ continue
+ }
+ if line == "--- /dev/null" && strings.HasPrefix(self.patch.header[idx+1], "+++ b/") {
+ line = "--- a/" + self.patch.header[idx+1][6:]
+ }
+ result = append(result, line)
+ }
+ return result
} else {
return self.patch.header
}
diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go
index 97f32688ec9..19febf9a637 100644
--- a/pkg/config/app_config.go
+++ b/pkg/config/app_config.go
@@ -370,6 +370,7 @@ type AppState struct {
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int
+ RenameSimilarityThreshold int
LocalBranchSortOrder string
RemoteBranchSortOrder string
@@ -385,15 +386,16 @@ type AppState struct {
func getDefaultAppState() *AppState {
return &AppState{
- LastUpdateCheck: 0,
- RecentRepos: []string{},
- StartupPopupVersion: 0,
- LastVersion: "",
- DiffContextSize: 3,
- LocalBranchSortOrder: "recency",
- RemoteBranchSortOrder: "alphabetical",
- GitLogOrder: "", // should be "topo-order" eventually
- GitLogShowGraph: "", // should be "always" eventually
+ LastUpdateCheck: 0,
+ RecentRepos: []string{},
+ StartupPopupVersion: 0,
+ LastVersion: "",
+ DiffContextSize: 3,
+ RenameSimilarityThreshold: 50,
+ LocalBranchSortOrder: "recency",
+ RemoteBranchSortOrder: "alphabetical",
+ GitLogOrder: "", // should be "topo-order" eventually
+ GitLogShowGraph: "", // should be "always" eventually
}
}
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index c225944613a..d08e4fda476 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -19,12 +19,9 @@ type UserConfig struct {
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
// If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
- // Keybindings
- Keybinding KeybindingConfig `yaml:"keybinding"`
// Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
OS OSConfig `yaml:"os,omitempty"`
// If true, don't display introductory popups upon opening Lazygit.
- // Lazygit sets this to true upon first runninng the program so that you don't see introductory popups every time you open the program.
DisableStartupPopups bool `yaml:"disableStartupPopups"`
// User-configured commands that can be invoked from within Lazygit
CustomCommands []CustomCommand `yaml:"customCommands" jsonschema:"uniqueItems=true"`
@@ -38,6 +35,8 @@ type UserConfig struct {
NotARepository string `yaml:"notARepository" jsonschema:"enum=prompt,enum=create,enum=skip,enum=quit"`
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
+ // Keybindings
+ Keybinding KeybindingConfig `yaml:"keybinding"`
}
type RefresherConfig struct {
@@ -78,6 +77,9 @@ type GuiConfig struct {
SidePanelWidth float64 `yaml:"sidePanelWidth" jsonschema:"maximum=1,minimum=0"`
// If true, increase the height of the focused side window; creating an accordion effect.
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
+ // The weight of the expanded side panel, relative to the other panels. 2 means
+ // twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true.
+ ExpandedSidePanelWeight int `yaml:"expandedSidePanelWeight"`
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
// Options are:
// - 'horizontal': split the window horizontally
@@ -123,10 +125,17 @@ type GuiConfig struct {
NerdFontsVersion string `yaml:"nerdFontsVersion" jsonschema:"enum=2,enum=3,enum="`
// If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.
ShowFileIcons bool `yaml:"showFileIcons"`
+ // Length of author name in (non-expanded) commits view. 2 means show initials only.
+ CommitAuthorShortLength int `yaml:"commitAuthorShortLength"`
+ // Length of author name in expanded commits view. 2 means show initials only.
+ CommitAuthorLongLength int `yaml:"commitAuthorLongLength"`
// Length of commit hash in commits view. 0 shows '*' if NF icons aren't on.
CommitHashLength int `yaml:"commitHashLength" jsonschema:"minimum=0"`
// If true, show commit hashes alongside branch names in the branches view.
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
+ // Whether to show the divergence from the base branch in the branches view.
+ // One of: 'none' | 'onlyArrow' | 'arrowAndNumber'
+ ShowDivergenceFromBaseBranch string `yaml:"showDivergenceFromBaseBranch" jsonschema:"enum=none,enum=onlyArrow,enum=arrowAndNumber"`
// Height of the command log view
CommandLogSize int `yaml:"commandLogSize" jsonschema:"minimum=0"`
// Whether to split the main window when viewing file changes.
@@ -170,6 +179,8 @@ type ThemeConfig struct {
// Background color of selected line.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedLineBgColor []string `yaml:"selectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"`
+ // Background color of selected line when view doesn't have focus.
+ InactiveViewSelectedLineBgColor []string `yaml:"inactiveViewSelectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"`
// Foreground color of copied commit
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor" jsonschema:"minItems=1,uniqueItems=true"`
// Background color of copied commit
@@ -215,8 +226,11 @@ type GitConfig struct {
FetchAll bool `yaml:"fetchAll"`
// Command used when displaying the current branch git log in the main window
BranchLogCmd string `yaml:"branchLogCmd"`
- // Command used to display git log of all branches in the main window
+ // Command used to display git log of all branches in the main window.
+ // Deprecated: User `allBranchesLogCmds` instead.
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
+ // Commands used to display git log of all branches in the main window, they will be cycled in order of appearance
+ AllBranchesLogCmds []string `yaml:"allBranchesLogCmds"`
// If true, do not spawn a separate process when using GPG
OverrideGpg bool `yaml:"overrideGpg"`
// If true, do not allow force pushes
@@ -225,6 +239,8 @@ type GitConfig struct {
CommitPrefix *CommitPrefixConfig `yaml:"commitPrefix"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
+ // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix
+ BranchPrefix string `yaml:"branchPrefix"`
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
// (This should really be under 'gui', not 'git')
ParseEmoji bool `yaml:"parseEmoji"`
@@ -252,7 +268,7 @@ type PagingConfig struct {
// diff-so-fancy
// delta --dark --paging=never
// ydiff -p cat -s --wrap --width={{columnWidth}}
- Pager PagerType `yaml:"pager" jsonschema:"minLength=1"`
+ Pager PagerType `yaml:"pager"`
// If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
UseConfig bool `yaml:"useConfig"`
// e.g. 'difft --color=always'
@@ -274,6 +290,8 @@ type MergingConfig struct {
ManualCommit bool `yaml:"manualCommit"`
// Extra args passed to `git merge`, e.g. --no-ff
Args string `yaml:"args" jsonschema:"example=--no-ff"`
+ // The commit message to use for a squash merge commit. Can contain "{{selectedRef}}" and "{{currentBranch}}" placeholders.
+ SquashMergeMessage string `yaml:"squashMergeMessage"`
}
type LogConfig struct {
@@ -294,9 +312,9 @@ type LogConfig struct {
type CommitPrefixConfig struct {
// pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
- Pattern string `yaml:"pattern" jsonschema:"example=^\\w+\\/(\\w+-\\w+).*,minLength=1"`
+ Pattern string `yaml:"pattern" jsonschema:"example=^\\w+\\/(\\w+-\\w+).*"`
// Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
- Replace string `yaml:"replace" jsonschema:"example=[$1] ,minLength=1"`
+ Replace string `yaml:"replace" jsonschema:"example=[$1]"`
}
type UpdateConfig struct {
@@ -323,73 +341,75 @@ type KeybindingConfig struct {
// damn looks like we have some inconsistencies here with -alt and -alt1
type KeybindingUniversalConfig struct {
- Quit string `yaml:"quit"`
- QuitAlt1 string `yaml:"quit-alt1"`
- Return string `yaml:"return"`
- QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
- TogglePanel string `yaml:"togglePanel"`
- PrevItem string `yaml:"prevItem"`
- NextItem string `yaml:"nextItem"`
- PrevItemAlt string `yaml:"prevItem-alt"`
- NextItemAlt string `yaml:"nextItem-alt"`
- PrevPage string `yaml:"prevPage"`
- NextPage string `yaml:"nextPage"`
- ScrollLeft string `yaml:"scrollLeft"`
- ScrollRight string `yaml:"scrollRight"`
- GotoTop string `yaml:"gotoTop"`
- GotoBottom string `yaml:"gotoBottom"`
- ToggleRangeSelect string `yaml:"toggleRangeSelect"`
- RangeSelectDown string `yaml:"rangeSelectDown"`
- RangeSelectUp string `yaml:"rangeSelectUp"`
- PrevBlock string `yaml:"prevBlock"`
- NextBlock string `yaml:"nextBlock"`
- PrevBlockAlt string `yaml:"prevBlock-alt"`
- NextBlockAlt string `yaml:"nextBlock-alt"`
- NextBlockAlt2 string `yaml:"nextBlock-alt2"`
- PrevBlockAlt2 string `yaml:"prevBlock-alt2"`
- JumpToBlock []string `yaml:"jumpToBlock"`
- NextMatch string `yaml:"nextMatch"`
- PrevMatch string `yaml:"prevMatch"`
- StartSearch string `yaml:"startSearch"`
- OptionMenu string `yaml:"optionMenu"`
- OptionMenuAlt1 string `yaml:"optionMenu-alt1"`
- Select string `yaml:"select"`
- GoInto string `yaml:"goInto"`
- Confirm string `yaml:"confirm"`
- ConfirmInEditor string `yaml:"confirmInEditor"`
- Remove string `yaml:"remove"`
- New string `yaml:"new"`
- Edit string `yaml:"edit"`
- OpenFile string `yaml:"openFile"`
- ScrollUpMain string `yaml:"scrollUpMain"`
- ScrollDownMain string `yaml:"scrollDownMain"`
- ScrollUpMainAlt1 string `yaml:"scrollUpMain-alt1"`
- ScrollDownMainAlt1 string `yaml:"scrollDownMain-alt1"`
- ScrollUpMainAlt2 string `yaml:"scrollUpMain-alt2"`
- ScrollDownMainAlt2 string `yaml:"scrollDownMain-alt2"`
- ExecuteCustomCommand string `yaml:"executeCustomCommand"`
- CreateRebaseOptionsMenu string `yaml:"createRebaseOptionsMenu"`
- Push string `yaml:"pushFiles"` // 'Files' appended for legacy reasons
- Pull string `yaml:"pullFiles"` // 'Files' appended for legacy reasons
- Refresh string `yaml:"refresh"`
- CreatePatchOptionsMenu string `yaml:"createPatchOptionsMenu"`
- NextTab string `yaml:"nextTab"`
- PrevTab string `yaml:"prevTab"`
- NextScreenMode string `yaml:"nextScreenMode"`
- PrevScreenMode string `yaml:"prevScreenMode"`
- Undo string `yaml:"undo"`
- Redo string `yaml:"redo"`
- FilteringMenu string `yaml:"filteringMenu"`
- DiffingMenu string `yaml:"diffingMenu"`
- DiffingMenuAlt string `yaml:"diffingMenu-alt"`
- CopyToClipboard string `yaml:"copyToClipboard"`
- OpenRecentRepos string `yaml:"openRecentRepos"`
- SubmitEditorText string `yaml:"submitEditorText"`
- ExtrasMenu string `yaml:"extrasMenu"`
- ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"`
- IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"`
- DecreaseContextInDiffView string `yaml:"decreaseContextInDiffView"`
- OpenDiffTool string `yaml:"openDiffTool"`
+ Quit string `yaml:"quit"`
+ QuitAlt1 string `yaml:"quit-alt1"`
+ Return string `yaml:"return"`
+ QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
+ TogglePanel string `yaml:"togglePanel"`
+ PrevItem string `yaml:"prevItem"`
+ NextItem string `yaml:"nextItem"`
+ PrevItemAlt string `yaml:"prevItem-alt"`
+ NextItemAlt string `yaml:"nextItem-alt"`
+ PrevPage string `yaml:"prevPage"`
+ NextPage string `yaml:"nextPage"`
+ ScrollLeft string `yaml:"scrollLeft"`
+ ScrollRight string `yaml:"scrollRight"`
+ GotoTop string `yaml:"gotoTop"`
+ GotoBottom string `yaml:"gotoBottom"`
+ ToggleRangeSelect string `yaml:"toggleRangeSelect"`
+ RangeSelectDown string `yaml:"rangeSelectDown"`
+ RangeSelectUp string `yaml:"rangeSelectUp"`
+ PrevBlock string `yaml:"prevBlock"`
+ NextBlock string `yaml:"nextBlock"`
+ PrevBlockAlt string `yaml:"prevBlock-alt"`
+ NextBlockAlt string `yaml:"nextBlock-alt"`
+ NextBlockAlt2 string `yaml:"nextBlock-alt2"`
+ PrevBlockAlt2 string `yaml:"prevBlock-alt2"`
+ JumpToBlock []string `yaml:"jumpToBlock"`
+ NextMatch string `yaml:"nextMatch"`
+ PrevMatch string `yaml:"prevMatch"`
+ StartSearch string `yaml:"startSearch"`
+ OptionMenu string `yaml:"optionMenu"`
+ OptionMenuAlt1 string `yaml:"optionMenu-alt1"`
+ Select string `yaml:"select"`
+ GoInto string `yaml:"goInto"`
+ Confirm string `yaml:"confirm"`
+ ConfirmInEditor string `yaml:"confirmInEditor"`
+ Remove string `yaml:"remove"`
+ New string `yaml:"new"`
+ Edit string `yaml:"edit"`
+ OpenFile string `yaml:"openFile"`
+ ScrollUpMain string `yaml:"scrollUpMain"`
+ ScrollDownMain string `yaml:"scrollDownMain"`
+ ScrollUpMainAlt1 string `yaml:"scrollUpMain-alt1"`
+ ScrollDownMainAlt1 string `yaml:"scrollDownMain-alt1"`
+ ScrollUpMainAlt2 string `yaml:"scrollUpMain-alt2"`
+ ScrollDownMainAlt2 string `yaml:"scrollDownMain-alt2"`
+ ExecuteCustomCommand string `yaml:"executeCustomCommand"`
+ CreateRebaseOptionsMenu string `yaml:"createRebaseOptionsMenu"`
+ Push string `yaml:"pushFiles"` // 'Files' appended for legacy reasons
+ Pull string `yaml:"pullFiles"` // 'Files' appended for legacy reasons
+ Refresh string `yaml:"refresh"`
+ CreatePatchOptionsMenu string `yaml:"createPatchOptionsMenu"`
+ NextTab string `yaml:"nextTab"`
+ PrevTab string `yaml:"prevTab"`
+ NextScreenMode string `yaml:"nextScreenMode"`
+ PrevScreenMode string `yaml:"prevScreenMode"`
+ Undo string `yaml:"undo"`
+ Redo string `yaml:"redo"`
+ FilteringMenu string `yaml:"filteringMenu"`
+ DiffingMenu string `yaml:"diffingMenu"`
+ DiffingMenuAlt string `yaml:"diffingMenu-alt"`
+ CopyToClipboard string `yaml:"copyToClipboard"`
+ OpenRecentRepos string `yaml:"openRecentRepos"`
+ SubmitEditorText string `yaml:"submitEditorText"`
+ ExtrasMenu string `yaml:"extrasMenu"`
+ ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"`
+ IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"`
+ DecreaseContextInDiffView string `yaml:"decreaseContextInDiffView"`
+ IncreaseRenameSimilarityThreshold string `yaml:"increaseRenameSimilarityThreshold"`
+ DecreaseRenameSimilarityThreshold string `yaml:"decreaseRenameSimilarityThreshold"`
+ OpenDiffTool string `yaml:"openDiffTool"`
}
type KeybindingStatusConfig struct {
@@ -554,8 +574,12 @@ type OSConfig struct {
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
// CopyToClipboardCmd is the command for copying to clipboard.
- // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard
+ // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
+
+ // ReadFromClipboardCmd is the command for reading the clipboard.
+ // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
+ ReadFromClipboardCmd string `yaml:"readFromClipboardCmd,omitempty"`
}
type CustomCommandAfterHook struct {
@@ -581,6 +605,8 @@ type CustomCommand struct {
Stream bool `yaml:"stream"`
// If true, show the command's output in a popup within Lazygit
ShowOutput bool `yaml:"showOutput"`
+ // The title to display in the popup panel if showOutput is true. If left unset, the command will be used as the title.
+ OutputTitle string `yaml:"outputTitle"`
// Actions to take after the command has completed
After CustomCommandAfterHook `yaml:"after"`
}
@@ -650,44 +676,50 @@ func GetDefaultConfig() *UserConfig {
SkipStashWarning: false,
SidePanelWidth: 0.3333,
ExpandFocusedSidePanel: false,
+ ExpandedSidePanelWeight: 2,
MainPanelSplitMode: "flexible",
EnlargedSideViewLocation: "left",
Language: "auto",
TimeFormat: "02 Jan 06",
ShortTimeFormat: time.Kitchen,
Theme: ThemeConfig{
- ActiveBorderColor: []string{"green", "bold"},
- SearchingActiveBorderColor: []string{"cyan", "bold"},
- InactiveBorderColor: []string{"default"},
- OptionsTextColor: []string{"blue"},
- SelectedLineBgColor: []string{"blue"},
- CherryPickedCommitBgColor: []string{"cyan"},
- CherryPickedCommitFgColor: []string{"blue"},
- MarkedBaseCommitBgColor: []string{"yellow"},
- MarkedBaseCommitFgColor: []string{"blue"},
- UnstagedChangesColor: []string{"red"},
- DefaultFgColor: []string{"default"},
+ ActiveBorderColor: []string{"green", "bold"},
+ SearchingActiveBorderColor: []string{"cyan", "bold"},
+ InactiveBorderColor: []string{"default"},
+ OptionsTextColor: []string{"blue"},
+ SelectedLineBgColor: []string{"blue"},
+ InactiveViewSelectedLineBgColor: []string{"bold"},
+ CherryPickedCommitBgColor: []string{"cyan"},
+ CherryPickedCommitFgColor: []string{"blue"},
+ MarkedBaseCommitBgColor: []string{"yellow"},
+ MarkedBaseCommitFgColor: []string{"blue"},
+ UnstagedChangesColor: []string{"red"},
+ DefaultFgColor: []string{"default"},
},
- CommitLength: CommitLengthConfig{Show: true},
- SkipNoStagedFilesWarning: false,
- ShowListFooter: true,
- ShowCommandLog: true,
- ShowBottomLine: true,
- ShowPanelJumps: true,
- ShowFileTree: true,
- ShowRandomTip: true,
- ShowIcons: false,
- NerdFontsVersion: "",
- ShowFileIcons: true,
- CommitHashLength: 8,
- ShowBranchCommitHash: false,
- CommandLogSize: 8,
- SplitDiff: "auto",
- SkipRewordInEditorWarning: false,
- Border: "rounded",
- AnimateExplosion: true,
- PortraitMode: "auto",
- FilterMode: "substring",
+ CommitLength: CommitLengthConfig{Show: true},
+ SkipNoStagedFilesWarning: false,
+ ShowListFooter: true,
+ ShowCommandLog: true,
+ ShowBottomLine: true,
+ ShowPanelJumps: true,
+ ShowFileTree: true,
+ ShowRandomTip: true,
+ ShowIcons: false,
+ NerdFontsVersion: "",
+ ShowFileIcons: true,
+ CommitAuthorShortLength: 2,
+ CommitAuthorLongLength: 17,
+ CommitHashLength: 8,
+ ShowBranchCommitHash: false,
+ ShowDivergenceFromBaseBranch: "none",
+ CommandLogSize: 8,
+ SplitDiff: "auto",
+ SkipRewordInEditorWarning: false,
+ WindowSize: "normal",
+ Border: "rounded",
+ AnimateExplosion: true,
+ PortraitMode: "auto",
+ FilterMode: "substring",
Spinner: SpinnerConfig{
Frames: []string{"|", "/", "-", "\\"},
Rate: 50,
@@ -707,8 +739,9 @@ func GetDefaultConfig() *UserConfig {
AutoWrapWidth: 72,
},
Merging: MergingConfig{
- ManualCommit: false,
- Args: "",
+ ManualCommit: false,
+ Args: "",
+ SquashMergeMessage: "Squash merge {{selectedRef}} into {{currentBranch}}",
},
Log: LogConfig{
Order: "topo-order",
@@ -724,6 +757,7 @@ func GetDefaultConfig() *UserConfig {
AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium",
DisableForcePushing: false,
CommitPrefixes: map[string]CommitPrefixConfig(nil),
+ BranchPrefix: "",
ParseEmoji: false,
TruncateCopiedCommitHashesTo: 12,
},
@@ -735,77 +769,85 @@ func GetDefaultConfig() *UserConfig {
Method: "prompt",
Days: 14,
},
- ConfirmOnQuit: false,
- QuitOnTopLevelReturn: false,
+ ConfirmOnQuit: false,
+ QuitOnTopLevelReturn: false,
+ OS: OSConfig{},
+ DisableStartupPopups: false,
+ CustomCommands: []CustomCommand(nil),
+ Services: map[string]string(nil),
+ NotARepository: "prompt",
+ PromptToReturnFromSubprocess: true,
Keybinding: KeybindingConfig{
Universal: KeybindingUniversalConfig{
- Quit: "q",
- QuitAlt1: "",
- Return: "",
- QuitWithoutChangingDirectory: "Q",
- TogglePanel: "",
- PrevItem: "",
- NextItem: "",
- PrevItemAlt: "k",
- NextItemAlt: "j",
- PrevPage: ",",
- NextPage: ".",
- ScrollLeft: "H",
- ScrollRight: "L",
- GotoTop: "<",
- GotoBottom: ">",
- ToggleRangeSelect: "v",
- RangeSelectDown: "",
- RangeSelectUp: "",
- PrevBlock: "",
- NextBlock: "",
- PrevBlockAlt: "h",
- NextBlockAlt: "l",
- PrevBlockAlt2: "",
- NextBlockAlt2: "",
- JumpToBlock: []string{"1", "2", "3", "4", "5"},
- NextMatch: "n",
- PrevMatch: "N",
- StartSearch: "/",
- OptionMenu: "",
- OptionMenuAlt1: "?",
- Select: "",
- GoInto: "",
- Confirm: "",
- ConfirmInEditor: "",
- Remove: "d",
- New: "n",
- Edit: "e",
- OpenFile: "o",
- OpenRecentRepos: "",
- ScrollUpMain: "",
- ScrollDownMain: "",
- ScrollUpMainAlt1: "K",
- ScrollDownMainAlt1: "J",
- ScrollUpMainAlt2: "",
- ScrollDownMainAlt2: "",
- ExecuteCustomCommand: ":",
- CreateRebaseOptionsMenu: "m",
- Push: "P",
- Pull: "p",
- Refresh: "R",
- CreatePatchOptionsMenu: "",
- NextTab: "]",
- PrevTab: "[",
- NextScreenMode: "+",
- PrevScreenMode: "_",
- Undo: "z",
- Redo: "",
- FilteringMenu: "",
- DiffingMenu: "W",
- DiffingMenuAlt: "",
- CopyToClipboard: "",
- SubmitEditorText: "",
- ExtrasMenu: "@",
- ToggleWhitespaceInDiffView: "",
- IncreaseContextInDiffView: "}",
- DecreaseContextInDiffView: "{",
- OpenDiffTool: "",
+ Quit: "q",
+ QuitAlt1: "",
+ Return: "",
+ QuitWithoutChangingDirectory: "Q",
+ TogglePanel: "",
+ PrevItem: "",
+ NextItem: "",
+ PrevItemAlt: "k",
+ NextItemAlt: "j",
+ PrevPage: ",",
+ NextPage: ".",
+ ScrollLeft: "H",
+ ScrollRight: "L",
+ GotoTop: "<",
+ GotoBottom: ">",
+ ToggleRangeSelect: "v",
+ RangeSelectDown: "",
+ RangeSelectUp: "",
+ PrevBlock: "",
+ NextBlock: "",
+ PrevBlockAlt: "h",
+ NextBlockAlt: "l",
+ PrevBlockAlt2: "",
+ NextBlockAlt2: "",
+ JumpToBlock: []string{"1", "2", "3", "4", "5"},
+ NextMatch: "n",
+ PrevMatch: "N",
+ StartSearch: "/",
+ OptionMenu: "",
+ OptionMenuAlt1: "?",
+ Select: "",
+ GoInto: "",
+ Confirm: "",
+ ConfirmInEditor: "",
+ Remove: "d",
+ New: "n",
+ Edit: "e",
+ OpenFile: "o",
+ OpenRecentRepos: "",
+ ScrollUpMain: "",
+ ScrollDownMain: "",
+ ScrollUpMainAlt1: "K",
+ ScrollDownMainAlt1: "J",
+ ScrollUpMainAlt2: "",
+ ScrollDownMainAlt2: "",
+ ExecuteCustomCommand: ":",
+ CreateRebaseOptionsMenu: "m",
+ Push: "P",
+ Pull: "p",
+ Refresh: "R",
+ CreatePatchOptionsMenu: "",
+ NextTab: "]",
+ PrevTab: "[",
+ NextScreenMode: "+",
+ PrevScreenMode: "_",
+ Undo: "z",
+ Redo: "",
+ FilteringMenu: "",
+ DiffingMenu: "W",
+ DiffingMenuAlt: "",
+ CopyToClipboard: "",
+ SubmitEditorText: "",
+ ExtrasMenu: "@",
+ ToggleWhitespaceInDiffView: "",
+ IncreaseContextInDiffView: "}",
+ DecreaseContextInDiffView: "{",
+ IncreaseRenameSimilarityThreshold: ")",
+ DecreaseRenameSimilarityThreshold: "(",
+ OpenDiffTool: "",
},
Status: KeybindingStatusConfig{
CheckForUpdate: "u",
@@ -903,11 +945,5 @@ func GetDefaultConfig() *UserConfig {
CommitMenu: "",
},
},
- OS: OSConfig{},
- DisableStartupPopups: false,
- CustomCommands: []CustomCommand(nil),
- Services: map[string]string(nil),
- NotARepository: "prompt",
- PromptToReturnFromSubprocess: true,
}
}
diff --git a/pkg/config/user_config_validation.go b/pkg/config/user_config_validation.go
index 945979db96b..403119adadb 100644
--- a/pkg/config/user_config_validation.go
+++ b/pkg/config/user_config_validation.go
@@ -7,7 +7,12 @@ import (
)
func (config *UserConfig) Validate() error {
- if err := validateEnum("gui.statusPanelView", config.Gui.StatusPanelView, []string{"dashboard", "allBranchesLog"}); err != nil {
+ if err := validateEnum("gui.statusPanelView", config.Gui.StatusPanelView,
+ []string{"dashboard", "allBranchesLog"}); err != nil {
+ return err
+ }
+ if err := validateEnum("gui.showDivergenceFromBaseBranch", config.Gui.ShowDivergenceFromBaseBranch,
+ []string{"none", "onlyArrow", "arrowAndNumber"}); err != nil {
return err
}
return nil
diff --git a/pkg/gui/background.go b/pkg/gui/background.go
index db267c0dcae..061502a43fa 100644
--- a/pkg/gui/background.go
+++ b/pkg/gui/background.go
@@ -1,6 +1,8 @@
package gui
import (
+ "fmt"
+ "runtime"
"strings"
"time"
@@ -46,6 +48,29 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
refreshInterval)
}
}
+
+ if self.gui.Config.GetDebug() {
+ self.goEvery(time.Second*time.Duration(10), self.gui.stopChan, func() error {
+ formatBytes := func(b uint64) string {
+ const unit = 1000
+ if b < unit {
+ return fmt.Sprintf("%d B", b)
+ }
+ div, exp := uint64(unit), 0
+ for n := b / unit; n >= unit; n /= unit {
+ div *= unit
+ exp++
+ }
+ return fmt.Sprintf("%.1f %cB",
+ float64(b)/float64(div), "kMGTPE"[exp])
+ }
+
+ m := runtime.MemStats{}
+ runtime.ReadMemStats(&m)
+ self.gui.c.Log.Infof("Heap memory in use: %s", formatBytes(m.HeapAlloc))
+ return nil
+ })
+ }
}
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
diff --git a/pkg/gui/context.go b/pkg/gui/context.go
index be5a720e331..5dc6df6a084 100644
--- a/pkg/gui/context.go
+++ b/pkg/gui/context.go
@@ -230,6 +230,10 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
self.gui.helpers.Window.SetWindowContext(c)
self.gui.helpers.Window.MoveToTopOfWindow(c)
+ oldView := self.gui.c.GocuiGui().CurrentView()
+ if oldView != nil && oldView.Name() != viewName {
+ oldView.HighlightInactive = true
+ }
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
return err
}
@@ -387,3 +391,12 @@ func (self *ContextMgr) ContextForKey(key types.ContextKey) types.Context {
return nil
}
+
+func (self *ContextMgr) PopupContexts() []types.Context {
+ self.RLock()
+ defer self.RUnlock()
+
+ return lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
+ return context.GetKind() == types.TEMPORARY_POPUP || context.GetKind() == types.PERSISTENT_POPUP
+ })
+}
diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go
index acece19942a..ca04a2fa955 100644
--- a/pkg/gui/context/base_context.go
+++ b/pkg/gui/context/base_context.go
@@ -20,11 +20,12 @@ type BaseContext struct {
onFocusFn onFocusFn
onFocusLostFn onFocusLostFn
- focusable bool
- transient bool
- hasControlledBounds bool
- needsRerenderOnWidthChange bool
- highlightOnFocus bool
+ focusable bool
+ transient bool
+ hasControlledBounds bool
+ needsRerenderOnWidthChange types.NeedsRerenderOnWidthChangeLevel
+ needsRerenderOnHeightChange bool
+ highlightOnFocus bool
*ParentContextMgr
}
@@ -37,15 +38,16 @@ type (
var _ types.IBaseContext = &BaseContext{}
type NewBaseContextOpts struct {
- Kind types.ContextKind
- Key types.ContextKey
- View *gocui.View
- WindowName string
- Focusable bool
- Transient bool
- HasUncontrolledBounds bool // negating for the sake of making false the default
- HighlightOnFocus bool
- NeedsRerenderOnWidthChange bool
+ Kind types.ContextKind
+ Key types.ContextKey
+ View *gocui.View
+ WindowName string
+ Focusable bool
+ Transient bool
+ HasUncontrolledBounds bool // negating for the sake of making false the default
+ HighlightOnFocus bool
+ NeedsRerenderOnWidthChange types.NeedsRerenderOnWidthChangeLevel
+ NeedsRerenderOnHeightChange bool
OnGetOptionsMap func() map[string]string
}
@@ -56,18 +58,19 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
hasControlledBounds := !opts.HasUncontrolledBounds
return &BaseContext{
- kind: opts.Kind,
- key: opts.Key,
- view: opts.View,
- windowName: opts.WindowName,
- onGetOptionsMap: opts.OnGetOptionsMap,
- focusable: opts.Focusable,
- transient: opts.Transient,
- hasControlledBounds: hasControlledBounds,
- highlightOnFocus: opts.HighlightOnFocus,
- needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
- ParentContextMgr: &ParentContextMgr{},
- viewTrait: viewTrait,
+ kind: opts.Kind,
+ key: opts.Key,
+ view: opts.View,
+ windowName: opts.WindowName,
+ onGetOptionsMap: opts.OnGetOptionsMap,
+ focusable: opts.Focusable,
+ transient: opts.Transient,
+ hasControlledBounds: hasControlledBounds,
+ highlightOnFocus: opts.HighlightOnFocus,
+ needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
+ needsRerenderOnHeightChange: opts.NeedsRerenderOnHeightChange,
+ ParentContextMgr: &ParentContextMgr{},
+ viewTrait: viewTrait,
}
}
@@ -130,6 +133,11 @@ func (self *BaseContext) AddMouseKeybindingsFn(fn types.MouseKeybindingsFn) {
self.mouseKeybindingsFns = append(self.mouseKeybindingsFns, fn)
}
+func (self *BaseContext) ClearAllBindingsFn() {
+ self.keybindingsFns = []types.KeybindingsFn{}
+ self.mouseKeybindingsFns = []types.MouseKeybindingsFn{}
+}
+
func (self *BaseContext) AddOnClickFn(fn func() error) {
if fn != nil {
self.onClickFn = fn
@@ -193,10 +201,14 @@ func (self *BaseContext) HasControlledBounds() bool {
return self.hasControlledBounds
}
-func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
+func (self *BaseContext) NeedsRerenderOnWidthChange() types.NeedsRerenderOnWidthChangeLevel {
return self.needsRerenderOnWidthChange
}
+func (self *BaseContext) NeedsRerenderOnHeightChange() bool {
+ return self.needsRerenderOnHeightChange
+}
+
func (self *BaseContext) Title() string {
return ""
}
diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go
index d2647ef843a..d289f2729b9 100644
--- a/pkg/gui/context/branches_context.go
+++ b/pkg/gui/context/branches_context.go
@@ -46,7 +46,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
- NeedsRerenderOnWidthChange: true,
+ NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES,
})),
ListRenderer: ListRenderer{
list: viewModel,
diff --git a/pkg/gui/context/commit_files_context.go b/pkg/gui/context/commit_files_context.go
index 7af968fb7a8..b56798feaa5 100644
--- a/pkg/gui/context/commit_files_context.go
+++ b/pkg/gui/context/commit_files_context.go
@@ -1,6 +1,7 @@
package context
import (
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -18,8 +19,9 @@ type CommitFilesContext struct {
}
var (
- _ types.IListContext = (*CommitFilesContext)(nil)
- _ types.DiffableContext = (*CommitFilesContext)(nil)
+ _ types.IListContext = (*CommitFilesContext)(nil)
+ _ types.DiffableContext = (*CommitFilesContext)(nil)
+ _ types.ISearchableContext = (*CommitFilesContext)(nil)
)
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
@@ -64,10 +66,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
},
}
- ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
- ctx.GetList().SetSelection(selectedLineIdx)
- return ctx.HandleFocus(types.OnFocusOpts{})
- }))
+ ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
return ctx
}
@@ -75,3 +74,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
func (self *CommitFilesContext) GetDiffTerminals() []string {
return []string{self.GetRef().RefName()}
}
+
+func (self *CommitFilesContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
+ return nil
+}
diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go
index f69b7ef74e4..402f9b12ee6 100644
--- a/pkg/gui/context/commit_message_context.go
+++ b/pkg/gui/context/commit_message_context.go
@@ -116,6 +116,8 @@ func (self *CommitMessageContext) SetPanelState(
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.CommitMenu),
})
+
+ self.c.Views().CommitDescription.Visible = true
}
func (self *CommitMessageContext) RenderCommitLength() {
diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go
index eb738e332d3..773f946a94d 100644
--- a/pkg/gui/context/list_context_trait.go
+++ b/pkg/gui/context/list_context_trait.go
@@ -18,6 +18,9 @@ type ListContextTrait struct {
// we should find out exactly which lines are now part of the path and refresh those.
// We should also keep track of the previous path and refresh those lines too.
refreshViewportOnChange bool
+ // If this is true, we only render the visible lines of the list. Useful for lists that can
+ // get very long, because it can save a lot of memory
+ renderOnlyVisibleLines bool
}
func (self *ListContextTrait) IsListContext() {}
@@ -25,7 +28,8 @@ func (self *ListContextTrait) IsListContext() {}
func (self *ListContextTrait) FocusLine() {
// Doing this at the end of the layout function because we need the view to be
// resized before we focus the line, otherwise if we're in accordion mode
- // the view could be squashed and won't how to adjust the cursor/origin
+ // the view could be squashed and won't how to adjust the cursor/origin.
+ // Also, refreshing the viewport needs to happen after the view has been resized.
self.c.AfterLayout(func() error {
oldOrigin, _ := self.GetViewTrait().ViewPortYBounds()
@@ -40,22 +44,18 @@ func (self *ListContextTrait) FocusLine() {
self.GetViewTrait().CancelRangeSelect()
}
- // If FocusPoint() caused the view to scroll (because the selected line
- // was out of view before), we need to rerender the view port again.
- // This can happen when pressing , or . to scroll by pages, or < or > to
- // jump to the top or bottom.
- newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
- if self.refreshViewportOnChange && oldOrigin != newOrigin {
+ if self.refreshViewportOnChange {
self.refreshViewport()
+ } else if self.renderOnlyVisibleLines {
+ newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
+ if oldOrigin != newOrigin {
+ return self.HandleRender()
+ }
}
return nil
})
self.setFooter()
-
- if self.refreshViewportOnChange {
- self.refreshViewport()
- }
}
func (self *ListContextTrait) refreshViewport() {
@@ -93,8 +93,21 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
func (self *ListContextTrait) HandleRender() error {
self.list.ClampSelection()
- content := self.renderLines(-1, -1)
- self.GetViewTrait().SetContent(content)
+ if self.renderOnlyVisibleLines {
+ // Rendering only the visible area can save a lot of cell memory for
+ // those views that support it.
+ totalLength := self.list.Len()
+ if self.getNonModelItems != nil {
+ totalLength += len(self.getNonModelItems())
+ }
+ self.GetViewTrait().SetContentLineCount(totalLength)
+ startIdx, length := self.GetViewTrait().ViewPortYBounds()
+ content := self.renderLines(startIdx, startIdx+length)
+ self.GetViewTrait().SetViewPortContentAndClearEverythingElse(content)
+ } else {
+ content := self.renderLines(-1, -1)
+ self.GetViewTrait().SetContent(content)
+ }
self.c.Render()
self.setFooter()
@@ -102,7 +115,7 @@ func (self *ListContextTrait) HandleRender() error {
}
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
- self.GetList().SetSelection(selectedLineIdx)
+ self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx))
return self.HandleFocus(types.OnFocusOpts{})
}
@@ -123,3 +136,7 @@ func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
func (self *ListContextTrait) RangeSelectEnabled() bool {
return true
}
+
+func (self *ListContextTrait) RenderOnlyVisibleLines() bool {
+ return self.renderOnlyVisibleLines
+}
diff --git a/pkg/gui/context/list_renderer.go b/pkg/gui/context/list_renderer.go
index f29407055a9..b89eccf8b22 100644
--- a/pkg/gui/context/list_renderer.go
+++ b/pkg/gui/context/list_renderer.go
@@ -35,6 +35,7 @@ type ListRenderer struct {
numNonModelItems int
viewIndicesByModelIndex []int
modelIndicesByViewIndex []int
+ columnPositions []int
}
func (self *ListRenderer) GetList() types.IList {
@@ -59,6 +60,10 @@ func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int {
return viewIndex
}
+func (self *ListRenderer) ColumnPositions() []int {
+ return self.columnPositions
+}
+
// startIdx and endIdx are view indices, not model indices. If you want to
// render the whole list, pass -1 for both.
func (self *ListRenderer) renderLines(startIdx int, endIdx int) string {
@@ -87,6 +92,7 @@ func (self *ListRenderer) renderLines(startIdx int, endIdx int) string {
lines, columnPositions := utils.RenderDisplayStrings(
self.getDisplayStrings(startModelIdx, endModelIdx),
columnAlignments)
+ self.columnPositions = columnPositions
lines = self.insertNonModelItems(nonModelItems, endIdx, startIdx, lines, columnPositions)
return strings.Join(lines, "\n")
}
diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go
index d3fc6e240b8..fcb9b00cace 100644
--- a/pkg/gui/context/local_commits_context.go
+++ b/pkg/gui/context/local_commits_context.go
@@ -2,8 +2,10 @@ package context
import (
"log"
+ "strings"
"time"
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -18,8 +20,9 @@ type LocalCommitsContext struct {
}
var (
- _ types.IListContext = (*LocalCommitsContext)(nil)
- _ types.DiffableContext = (*LocalCommitsContext)(nil)
+ _ types.IListContext = (*LocalCommitsContext)(nil)
+ _ types.DiffableContext = (*LocalCommitsContext)(nil)
+ _ types.ISearchableContext = (*LocalCommitsContext)(nil)
)
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
@@ -69,12 +72,13 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
SearchTrait: NewSearchTrait(c),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
- View: c.Views().Commits,
- WindowName: "commits",
- Key: LOCAL_COMMITS_CONTEXT_KEY,
- Kind: types.SIDE_CONTEXT,
- Focusable: true,
- NeedsRerenderOnWidthChange: true,
+ View: c.Views().Commits,
+ WindowName: "commits",
+ Key: LOCAL_COMMITS_CONTEXT_KEY,
+ Kind: types.SIDE_CONTEXT,
+ Focusable: true,
+ NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
+ NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
@@ -82,13 +86,11 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
},
c: c,
refreshViewportOnChange: true,
+ renderOnlyVisibleLines: true,
},
}
- ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
- ctx.GetList().SetSelection(selectedLineIdx)
- return ctx.HandleFocus(types.OnFocusOpts{})
- }))
+ ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
return ctx
}
@@ -155,6 +157,10 @@ func (self *LocalCommitsContext) GetDiffTerminals() []string {
return []string{itemId}
}
+func (self *LocalCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
+ return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
+}
+
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
self.limitCommits = value
}
@@ -194,3 +200,18 @@ func shouldShowGraph(c *ContextCommon) bool {
log.Fatalf("Unknown value for git.log.showGraph: %s. Expected one of: 'always', 'never', 'when-maximised'", value)
return false
}
+
+func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPositions []int, searchStr string) []gocui.SearchPosition {
+ normalize := lo.Ternary(caseSensitive, func(s string) string { return s }, strings.ToLower)
+ return lo.FilterMap(commits, func(commit *models.Commit, idx int) (gocui.SearchPosition, bool) {
+ // The XStart and XEnd values are only used if the search string can't
+ // be found in the view. This can really only happen if the user is
+ // searching for a commit hash that is longer than the truncated hash
+ // that we render. So we just set the XStart and XEnd values to the
+ // start and end of the commit hash column, which is the second one.
+ result := gocui.SearchPosition{XStart: columnPositions[1], XEnd: columnPositions[2] - 1, Y: idx}
+ return result, strings.Contains(normalize(commit.Hash), searchStr) ||
+ strings.Contains(normalize(commit.Name), searchStr) ||
+ strings.Contains(normalize(commit.ExtraInfo), searchStr) // allow searching for tags
+ })
+}
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index a6b0e77cbc8..f1438b22165 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -50,6 +50,8 @@ func NewMenuContext(
type MenuViewModel struct {
c *ContextCommon
menuItems []*types.MenuItem
+ prompt string
+ promptLines []string
columnAlignment []utils.Alignment
*FilteredListViewModel[*types.MenuItem]
}
@@ -73,6 +75,23 @@ func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment
self.columnAlignment = columnAlignment
}
+func (self *MenuViewModel) GetPrompt() string {
+ return self.prompt
+}
+
+func (self *MenuViewModel) SetPrompt(prompt string) {
+ self.prompt = prompt
+ self.promptLines = nil
+}
+
+func (self *MenuViewModel) GetPromptLines() []string {
+ return self.promptLines
+}
+
+func (self *MenuViewModel) SetPromptLines(promptLines []string) {
+ self.promptLines = promptLines
+}
+
// TODO: move into presentation package
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
menuItems := self.FilteredListViewModel.GetItems()
@@ -88,20 +107,42 @@ func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
keyLabel = style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key))
}
- displayStrings = utils.Prepend(displayStrings, keyLabel)
+ checkMark := ""
+ switch item.Widget {
+ case types.MenuWidgetNone:
+ // do nothing
+ case types.MenuWidgetRadioButtonSelected:
+ checkMark = "(•)"
+ case types.MenuWidgetRadioButtonUnselected:
+ checkMark = "( )"
+ case types.MenuWidgetCheckboxSelected:
+ checkMark = "[✓]"
+ case types.MenuWidgetCheckboxUnselected:
+ checkMark = "[ ]"
+ }
+
+ displayStrings = utils.Prepend(displayStrings, keyLabel, checkMark)
return displayStrings
})
}
func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
+ result := []*NonModelItem{}
+ result = append(result, lo.Map(self.promptLines, func(line string, _ int) *NonModelItem {
+ return &NonModelItem{
+ Index: 0,
+ Column: 0,
+ Content: line,
+ }
+ })...)
+
// Don't display section headers when we are filtering, and the filter mode
// is fuzzy. The reason is that filtering changes the order of the items
// (they are sorted by best match), so all the sections would be messed up.
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig.Gui.UseFuzzySearch() {
- return []*NonModelItem{}
+ return result
}
- result := []*NonModelItem{}
menuItems := self.FilteredListViewModel.GetItems()
var prevSection *types.MenuSection = nil
for i, menuItem := range menuItems {
diff --git a/pkg/gui/context/patch_explorer_context.go b/pkg/gui/context/patch_explorer_context.go
index 34f70e2c722..ee856c5d2d1 100644
--- a/pkg/gui/context/patch_explorer_context.go
+++ b/pkg/gui/context/patch_explorer_context.go
@@ -18,7 +18,10 @@ type PatchExplorerContext struct {
mutex *deadlock.Mutex
}
-var _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil)
+var (
+ _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil)
+ _ types.ISearchableContext = (*PatchExplorerContext)(nil)
+)
func NewPatchExplorerContext(
view *gocui.View,
@@ -139,3 +142,7 @@ func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
return self.mutex
}
+
+func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
+ return nil
+}
diff --git a/pkg/gui/context/reflog_commits_context.go b/pkg/gui/context/reflog_commits_context.go
index 57ca7c4dc39..cec54988d01 100644
--- a/pkg/gui/context/reflog_commits_context.go
+++ b/pkg/gui/context/reflog_commits_context.go
@@ -48,7 +48,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
- NeedsRerenderOnWidthChange: true,
+ NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
})),
ListRenderer: ListRenderer{
list: viewModel,
diff --git a/pkg/gui/context/remote_branches_context.go b/pkg/gui/context/remote_branches_context.go
index 884d3debbf1..892953f8256 100644
--- a/pkg/gui/context/remote_branches_context.go
+++ b/pkg/gui/context/remote_branches_context.go
@@ -37,13 +37,13 @@ func NewRemoteBranchesContext(
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
- View: c.Views().RemoteBranches,
- WindowName: "branches",
- Key: REMOTE_BRANCHES_CONTEXT_KEY,
- Kind: types.SIDE_CONTEXT,
- Focusable: true,
- Transient: true,
- NeedsRerenderOnWidthChange: true,
+ View: c.Views().RemoteBranches,
+ WindowName: "branches",
+ Key: REMOTE_BRANCHES_CONTEXT_KEY,
+ Kind: types.SIDE_CONTEXT,
+ Focusable: true,
+ Transient: true,
+ NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
diff --git a/pkg/gui/context/simple_context.go b/pkg/gui/context/simple_context.go
index 7c00e09f741..cef871cefea 100644
--- a/pkg/gui/context/simple_context.go
+++ b/pkg/gui/context/simple_context.go
@@ -52,6 +52,8 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
}
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
+ self.GetViewTrait().SetHighlight(false)
+ _ = self.view.SetOriginX(0)
if self.onFocusLostFn != nil {
return self.onFocusLostFn(opts)
}
diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go
index f540dba8720..ddbb380c549 100644
--- a/pkg/gui/context/sub_commits_context.go
+++ b/pkg/gui/context/sub_commits_context.go
@@ -4,6 +4,7 @@ import (
"fmt"
"time"
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -21,8 +22,9 @@ type SubCommitsContext struct {
}
var (
- _ types.IListContext = (*SubCommitsContext)(nil)
- _ types.DiffableContext = (*SubCommitsContext)(nil)
+ _ types.IListContext = (*SubCommitsContext)(nil)
+ _ types.DiffableContext = (*SubCommitsContext)(nil)
+ _ types.ISearchableContext = (*SubCommitsContext)(nil)
)
func NewSubCommitsContext(
@@ -73,8 +75,6 @@ func NewSubCommitsContext(
selectedCommitHash,
startIdx,
endIdx,
- // Don't show the graph in the left/right view; we'd like to, but
- // it's too complicated:
shouldShowGraph(c),
git_commands.NewNullBisectInfo(),
false,
@@ -115,13 +115,14 @@ func NewSubCommitsContext(
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
- View: c.Views().SubCommits,
- WindowName: "branches",
- Key: SUB_COMMITS_CONTEXT_KEY,
- Kind: types.SIDE_CONTEXT,
- Focusable: true,
- Transient: true,
- NeedsRerenderOnWidthChange: true,
+ View: c.Views().SubCommits,
+ WindowName: "branches",
+ Key: SUB_COMMITS_CONTEXT_KEY,
+ Kind: types.SIDE_CONTEXT,
+ Focusable: true,
+ Transient: true,
+ NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
+ NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
@@ -130,13 +131,11 @@ func NewSubCommitsContext(
},
c: c,
refreshViewportOnChange: true,
+ renderOnlyVisibleLines: true,
},
}
- ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
- ctx.GetList().SetSelection(selectedLineIdx)
- return ctx.HandleFocus(types.OnFocusOpts{})
- }))
+ ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
return ctx
}
@@ -204,3 +203,7 @@ func (self *SubCommitsContext) GetDiffTerminals() []string {
return []string{itemId}
}
+
+func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
+ return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
+}
diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go
index 1179a8b1486..5342071ef88 100644
--- a/pkg/gui/context/view_trait.go
+++ b/pkg/gui/context/view_trait.go
@@ -34,12 +34,22 @@ func (self *ViewTrait) SetViewPortContent(content string) {
self.view.OverwriteLines(y, content)
}
+func (self *ViewTrait) SetViewPortContentAndClearEverythingElse(content string) {
+ _, y := self.view.Origin()
+ self.view.OverwriteLinesAndClearEverythingElse(y, content)
+}
+
+func (self *ViewTrait) SetContentLineCount(lineCount int) {
+ self.view.SetContentLineCount(lineCount)
+}
+
func (self *ViewTrait) SetContent(content string) {
self.view.SetContent(content)
}
func (self *ViewTrait) SetHighlight(highlight bool) {
self.view.Highlight = highlight
+ self.view.HighlightInactive = false
}
func (self *ViewTrait) SetFooter(value string) {
diff --git a/pkg/gui/context/working_tree_context.go b/pkg/gui/context/working_tree_context.go
index 6fa462cb1fc..76eb56d5767 100644
--- a/pkg/gui/context/working_tree_context.go
+++ b/pkg/gui/context/working_tree_context.go
@@ -1,6 +1,7 @@
package context
import (
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -15,7 +16,10 @@ type WorkingTreeContext struct {
*SearchTrait
}
-var _ types.IListContext = (*WorkingTreeContext)(nil)
+var (
+ _ types.IListContext = (*WorkingTreeContext)(nil)
+ _ types.ISearchableContext = (*WorkingTreeContext)(nil)
+)
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(
@@ -51,10 +55,11 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
},
}
- ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
- ctx.GetList().SetSelection(selectedLineIdx)
- return ctx.HandleFocus(types.OnFocusOpts{})
- }))
+ ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
return ctx
}
+
+func (self *WorkingTreeContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
+ return nil
+}
diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go
index 1dbf9b7d7d8..277098f3789 100644
--- a/pkg/gui/controllers.go
+++ b/pkg/gui/controllers.go
@@ -20,6 +20,10 @@ func (gui *Gui) Helpers() *helpers.Helpers {
// in the keybinding menu: the earlier that the controller is attached to a context,
// the lower in the list the keybindings will appear.
func (gui *Gui) resetHelpersAndControllers() {
+ for _, context := range gui.Contexts().Flatten() {
+ context.ClearAllBindingsFn()
+ }
+
helperCommon := gui.c
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
reposHelper := helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo)
@@ -175,6 +179,7 @@ func (gui *Gui) resetHelpersAndControllers() {
undoController := controllers.NewUndoController(common)
globalController := controllers.NewGlobalController(common)
contextLinesController := controllers.NewContextLinesController(common)
+ renameSimilarityThresholdController := controllers.NewRenameSimilarityThresholdController(common)
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common, &gui.viewBufferManagerMap)
branchesController := controllers.NewBranchesController(common)
@@ -379,6 +384,7 @@ func (gui *Gui) resetHelpersAndControllers() {
undoController,
globalController,
contextLinesController,
+ renameSimilarityThresholdController,
jumpToSideWindowController,
syncController,
)
@@ -400,7 +406,6 @@ func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) fu
view := getView()
view.ClearTextArea()
view.TextArea.TypeString(text)
- gui.helpers.Confirmation.ResizeCommitMessagePanels()
view.RenderTextArea()
}
}
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index d7faa78118d..f0d0899a591 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -100,22 +100,22 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
DisplayOnScreen: true,
},
{
- Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
- Handler: opts.Guards.OutsideFilterMode(self.rebase),
- GetDisabledReason: self.require(
- self.singleItemSelected(self.notRebasingOntoSelf),
- ),
- Description: self.c.Tr.RebaseBranch,
- Tooltip: self.c.Tr.RebaseBranchTooltip,
- DisplayOnScreen: true,
+ Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
+ Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)),
+ GetDisabledReason: self.require(self.singleItemSelected()),
+ Description: self.c.Tr.RebaseBranch,
+ Tooltip: self.c.Tr.RebaseBranchTooltip,
+ OpensMenu: true,
+ DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
Handler: opts.Guards.OutsideFilterMode(self.merge),
- GetDisabledReason: self.require(self.singleItemSelected()),
+ GetDisabledReason: self.require(self.singleItemSelected(self.notMergingIntoYourself)),
Description: self.c.Tr.Merge,
Tooltip: self.c.Tr.MergeBranchTooltip,
DisplayOnScreen: true,
+ OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Branches.FastForward),
@@ -159,6 +159,14 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
OpensMenu: true,
DisplayOnScreen: true,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
+ Handler: self.withItem(func(selectedBranch *models.Branch) error {
+ return self.c.Helpers().Diff.OpenDiffToolForRef(selectedBranch)
+ }),
+ GetDisabledReason: self.require(self.singleItemSelected()),
+ Description: self.c.Tr.OpenDiffTool,
+ },
}
}
@@ -205,6 +213,40 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
},
}
+ var disabledReason *types.DisabledReason
+ baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(selectedBranch, self.c.Model().MainBranches)
+ if err != nil {
+ return err
+ }
+ if baseBranch == "" {
+ baseBranch = self.c.Tr.CouldNotDetermineBaseBranch
+ disabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch}
+ }
+ shortBaseBranchName := helpers.ShortBranchName(baseBranch)
+ label := utils.ResolvePlaceholderString(
+ self.c.Tr.ViewDivergenceFromBaseBranch,
+ map[string]string{"baseBranch": shortBaseBranchName},
+ )
+ viewDivergenceFromBaseBranchItem := &types.MenuItem{
+ LabelColumns: []string{label},
+ Key: 'b',
+ OnPress: func() error {
+ branch := self.context().GetSelected()
+ if branch == nil {
+ return nil
+ }
+
+ return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
+ Ref: branch,
+ TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), shortBaseBranchName),
+ RefToShowDivergenceFrom: baseBranch,
+ Context: self.context(),
+ ShowBranchHeads: false,
+ })
+ },
+ DisabledReason: disabledReason,
+ }
+
unsetUpstreamItem := &types.MenuItem{
LabelColumns: []string{self.c.Tr.UnsetUpstream},
OnPress: func() error {
@@ -312,6 +354,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
options := []*types.MenuItem{
viewDivergenceItem,
+ viewDivergenceFromBaseBranchItem,
unsetUpstreamItem,
setUpstreamItem,
upstreamResetItem,
@@ -598,19 +641,8 @@ func (self *BranchesController) merge() error {
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
}
-func (self *BranchesController) rebase() error {
- selectedBranchName := self.context().GetSelected().Name
- return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
-}
-
-func (self *BranchesController) notRebasingOntoSelf(branch *models.Branch) *types.DisabledReason {
- selectedBranchName := branch.Name
- checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
- if selectedBranchName == checkedOutBranch {
- return &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
- }
-
- return nil
+func (self *BranchesController) rebase(branch *models.Branch) error {
+ return self.c.Helpers().MergeAndRebase.RebaseOntoRef(branch.Name)
}
func (self *BranchesController) fastForward(branch *models.Branch) error {
@@ -673,7 +705,8 @@ func (self *BranchesController) createSortMenu() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
}
return nil
- })
+ },
+ self.c.GetAppState().LocalBranchSortOrder)
}
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
@@ -802,3 +835,14 @@ func (self *BranchesController) branchIsReal(branch *models.Branch) *types.Disab
return nil
}
+
+func (self *BranchesController) notMergingIntoYourself(branch *models.Branch) *types.DisabledReason {
+ selectedBranchName := branch.Name
+ checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
+
+ if checkedOutBranch == selectedBranchName {
+ return &types.DisabledReason{Text: self.c.Tr.CantMergeBranchIntoItself}
+ }
+
+ return nil
+}
diff --git a/pkg/gui/controllers/commit_description_controller.go b/pkg/gui/controllers/commit_description_controller.go
index 0c078382b1f..80608fff504 100644
--- a/pkg/gui/controllers/commit_description_controller.go
+++ b/pkg/gui/controllers/commit_description_controller.go
@@ -53,7 +53,7 @@ func (self *CommitDescriptionController) context() *context.CommitMessageContext
}
func (self *CommitDescriptionController) switchToCommitMessage() error {
- return self.c.PushContext(self.c.Contexts().CommitMessage)
+ return self.c.ReplaceContext(self.c.Contexts().CommitMessage)
}
func (self *CommitDescriptionController) close() error {
diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go
index 4012dc9504b..5c37b4da9e8 100644
--- a/pkg/gui/controllers/commit_message_controller.go
+++ b/pkg/gui/controllers/commit_message_controller.go
@@ -85,7 +85,7 @@ func (self *CommitMessageController) handleNextCommit() error {
}
func (self *CommitMessageController) switchToCommitDescription() error {
- if err := self.c.PushContext(self.c.Contexts().CommitDescription); err != nil {
+ if err := self.c.ReplaceContext(self.c.Contexts().CommitDescription); err != nil {
return err
}
return nil
diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go
index 2d57f0ac039..867e4528a14 100644
--- a/pkg/gui/controllers/custom_patch_options_menu_action.go
+++ b/pkg/gui/controllers/custom_patch_options_menu_action.go
@@ -214,10 +214,13 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
PreserveMessage: false,
OnConfirm: func(summary string, description string) error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
- _ = self.c.Helpers().Commits.PopCommitMessageContexts()
+ _ = self.c.Helpers().Commits.CloseCommitMessagePanel()
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex, summary, description)
- return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
+ if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
+ return err
+ }
+ return self.c.PushContext(self.c.Contexts().LocalCommits)
})
},
},
@@ -234,7 +237,7 @@ func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
action = "Apply patch in reverse"
}
self.c.LogAction(action)
- if err := self.c.Git().Patch.ApplyCustomPatch(reverse); err != nil {
+ if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index b71991fc271..39730ae0935 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -1009,10 +1009,14 @@ func normalisedSelectedNodes(selectedNodes []*filetree.FileNode) []*filetree.Fil
func isDescendentOfSelectedNodes(node *filetree.FileNode, selectedNodes []*filetree.FileNode) bool {
for _, selectedNode := range selectedNodes {
+ if selectedNode.IsFile() {
+ continue
+ }
+
selectedNodePath := selectedNode.GetPath()
nodePath := node.GetPath()
- if strings.HasPrefix(nodePath, selectedNodePath) && nodePath != selectedNodePath {
+ if strings.HasPrefix(nodePath, selectedNodePath+"/") {
return true
}
}
@@ -1062,60 +1066,65 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
selectedNodes = normalisedSelectedNodes(selectedNodes)
- menuItems := []*types.MenuItem{
- {
- Label: self.c.Tr.DiscardAllChanges,
- OnPress: func() error {
- self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInFile)
+ discardAllChangesItem := types.MenuItem{
+ Label: self.c.Tr.DiscardAllChanges,
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInFile)
- if self.context().IsSelectingRange() {
- defer self.context().CancelRangeSelect()
- }
+ if self.context().IsSelectingRange() {
+ defer self.context().CancelRangeSelect()
+ }
- for _, node := range selectedNodes {
- if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
- return err
- }
+ for _, node := range selectedNodes {
+ if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
+ return err
}
+ }
- return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
- },
- Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig.Keybinding.Files.ConfirmDiscard),
- Tooltip: utils.ResolvePlaceholderString(
- self.c.Tr.DiscardAllTooltip,
- map[string]string{
- "path": self.formattedPaths(selectedNodes),
- },
- ),
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
},
+ Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig.Keybinding.Files.ConfirmDiscard),
+ Tooltip: utils.ResolvePlaceholderString(
+ self.c.Tr.DiscardAllTooltip,
+ map[string]string{
+ "path": self.formattedPaths(selectedNodes),
+ },
+ ),
}
- if someNodesHaveStagedChanges(selectedNodes) && someNodesHaveUnstagedChanges(selectedNodes) {
- menuItems = append(menuItems, &types.MenuItem{
- Label: self.c.Tr.DiscardUnstagedChanges,
- OnPress: func() error {
- self.c.LogAction(self.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
+ discardUnstagedChangesItem := types.MenuItem{
+ Label: self.c.Tr.DiscardUnstagedChanges,
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
- if self.context().IsSelectingRange() {
- defer self.context().CancelRangeSelect()
- }
+ if self.context().IsSelectingRange() {
+ defer self.context().CancelRangeSelect()
+ }
- for _, node := range selectedNodes {
- if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
- return err
- }
+ for _, node := range selectedNodes {
+ if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
+ return err
}
+ }
- return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
+ },
+ Key: 'u',
+ Tooltip: utils.ResolvePlaceholderString(
+ self.c.Tr.DiscardUnstagedTooltip,
+ map[string]string{
+ "path": self.formattedPaths(selectedNodes),
},
- Key: 'u',
- Tooltip: utils.ResolvePlaceholderString(
- self.c.Tr.DiscardUnstagedTooltip,
- map[string]string{
- "path": self.formattedPaths(selectedNodes),
- },
- ),
- })
+ ),
+ }
+
+ if !someNodesHaveStagedChanges(selectedNodes) || !someNodesHaveUnstagedChanges(selectedNodes) {
+ discardUnstagedChangesItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.DiscardUnstagedDisabled}
+ }
+
+ menuItems := []*types.MenuItem{
+ &discardAllChangesItem,
+ &discardUnstagedChangesItem,
}
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.DiscardChangesTitle, Items: menuItems})
diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go
index d9d6dbd9ae5..c07d1d72bb0 100644
--- a/pkg/gui/controllers/helpers/branches_helper.go
+++ b/pkg/gui/controllers/helpers/branches_helper.go
@@ -1,6 +1,8 @@
package helpers
import (
+ "strings"
+
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -44,3 +46,7 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName st
},
})
}
+
+func ShortBranchName(fullBranchName string) string {
+ return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
+}
diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go
index 6e1a181c77d..be3a36e8dd2 100644
--- a/pkg/gui/controllers/helpers/commits_helper.go
+++ b/pkg/gui/controllers/helpers/commits_helper.go
@@ -154,7 +154,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
self.UpdateCommitPanelView(opts.InitialMessage)
- return self.pushCommitMessageContexts()
+ return self.c.PushContext(self.c.Contexts().CommitMessage)
}
func (self *CommitsHelper) OnCommitSuccess() {
@@ -190,28 +190,10 @@ func (self *CommitsHelper) CloseCommitMessagePanel() error {
self.c.Contexts().CommitMessage.SetHistoryMessage("")
- return self.PopCommitMessageContexts()
-}
-
-func (self *CommitsHelper) PopCommitMessageContexts() error {
- return self.c.RemoveContexts(self.commitMessageContexts())
-}
-
-func (self *CommitsHelper) pushCommitMessageContexts() error {
- for _, context := range self.commitMessageContexts() {
- if err := self.c.PushContext(context); err != nil {
- return err
- }
- }
+ self.c.Views().CommitMessage.Visible = false
+ self.c.Views().CommitDescription.Visible = false
- return nil
-}
-
-func (self *CommitsHelper) commitMessageContexts() []types.Context {
- return []types.Context{
- self.c.Contexts().CommitDescription,
- self.c.Contexts().CommitMessage,
- }
+ return self.c.PopContext()
}
func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.Suggestion) error {
@@ -238,6 +220,13 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S
},
Key: 'c',
},
+ {
+ Label: self.c.Tr.PasteCommitMessageFromClipboard,
+ OnPress: func() error {
+ return self.pasteCommitMessageFromClipboard()
+ },
+ Key: 'p',
+ },
}
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.CommitMenuTitle,
@@ -257,3 +246,28 @@ func (self *CommitsHelper) addCoAuthor(suggestionFunc func(string) []*types.Sugg
},
})
}
+
+func (self *CommitsHelper) pasteCommitMessageFromClipboard() error {
+ message, err := self.c.OS().PasteFromClipboard()
+ if err != nil {
+ return err
+ }
+ if message == "" {
+ return nil
+ }
+
+ if currentMessage := self.JoinCommitMessageAndUnwrappedDescription(); currentMessage == "" {
+ self.SetMessageAndDescriptionInView(message)
+ return nil
+ }
+
+ // Confirm before overwriting the commit message
+ return self.c.Confirm(types.ConfirmOpts{
+ Title: self.c.Tr.PasteCommitMessageFromClipboard,
+ Prompt: self.c.Tr.SurePasteCommitMessage,
+ HandleConfirm: func() error {
+ self.SetMessageAndDescriptionInView(message)
+ return nil
+ },
+ })
+}
diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go
index b5a337fbb05..c0fb5bafb82 100644
--- a/pkg/gui/controllers/helpers/confirmation_helper.go
+++ b/pkg/gui/controllers/helpers/confirmation_helper.go
@@ -5,8 +5,6 @@ import (
"fmt"
"strings"
- "github.com/jesseduffield/gocui"
-
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@@ -63,15 +61,20 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
// Temporary hack: we're just duplicating the logic in `gocui.lineWrap`
func getMessageHeight(wrap bool, message string, width int) int {
+ return len(wrapMessageToWidth(wrap, message, width))
+}
+
+func wrapMessageToWidth(wrap bool, message string, width int) []string {
+ lines := strings.Split(message, "\n")
if !wrap {
- return len(strings.Split(message, "\n"))
+ return lines
}
- lineCount := 0
- lines := strings.Split(message, "\n")
+ wrappedLines := make([]string, 0, len(lines))
for _, line := range lines {
n := 0
+ offset := 0
lastWhitespaceIndex := -1
for i, currChr := range line {
rw := runewidth.RuneWidth(currChr)
@@ -79,45 +82,57 @@ func getMessageHeight(wrap bool, message string, width int) int {
if n > width {
if currChr == ' ' {
+ wrappedLines = append(wrappedLines, line[offset:i])
+ offset = i + 1
n = 0
} else if currChr == '-' {
+ wrappedLines = append(wrappedLines, line[offset:i])
+ offset = i
n = rw
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
if line[lastWhitespaceIndex] == '-' {
+ wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex+1])
+ offset = lastWhitespaceIndex + 1
n = i - lastWhitespaceIndex
} else {
+ wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex])
+ offset = lastWhitespaceIndex + 1
n = i - lastWhitespaceIndex + 1
}
} else {
+ wrappedLines = append(wrappedLines, line[offset:i])
+ offset = i
n = rw
}
- lineCount++
lastWhitespaceIndex = -1
} else if currChr == ' ' || currChr == '-' {
lastWhitespaceIndex = i
}
}
- lineCount++
+
+ wrappedLines = append(wrappedLines, line[offset:])
}
- return lineCount
+ return wrappedLines
}
-func (self *ConfirmationHelper) getPopupPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
- panelWidth := self.getPopupPanelWidth()
- panelHeight := getMessageHeight(wrap, prompt, panelWidth)
- return self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
+func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int, parentPopupContext types.Context) (int, int, int, int) {
+ return self.getPopupPanelDimensionsAux(panelWidth, contentHeight, parentPopupContext)
}
-func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
- return self.getPopupPanelDimensionsAux(panelWidth, contentHeight)
-}
-
-func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
+func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panelHeight int, parentPopupContext types.Context) (int, int, int, int) {
width, height := self.c.GocuiGui().Size()
if panelHeight > height*3/4 {
panelHeight = height * 3 / 4
}
+ if parentPopupContext != nil {
+ // If there's already a popup on the screen, offset the new one from its
+ // parent so that it's clearly distinguished from the parent
+ x0, y0, _, _ := parentPopupContext.GetView().Dimensions()
+ x0 += 2
+ y0 += 1
+ return x0, y0, x0 + panelWidth, y0 + panelHeight + 1
+ }
return width/2 - panelWidth/2,
height/2 - panelHeight/2 - panelHeight%2 - 1,
width/2 + panelWidth/2,
@@ -162,7 +177,6 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
suggestionsView.Subtitle = ""
}
- self.ResizeConfirmationPanel()
return nil
}
@@ -212,7 +226,6 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
- self.ResizeConfirmationPanel()
confirmationView.RenderTextArea()
} else {
self.c.ResetViewOrigin(confirmationView)
@@ -310,55 +323,31 @@ func (self *ConfirmationHelper) getSelectedSuggestionValue() string {
return ""
}
-func (self *ConfirmationHelper) ResizeConfirmationPanel() {
- suggestionsViewHeight := 0
- if self.c.Views().Suggestions.Visible {
- suggestionsViewHeight = 11
- }
- panelWidth := self.getPopupPanelWidth()
- prompt := self.c.Views().Confirmation.Buffer()
- wrap := true
- if self.c.Views().Confirmation.Editable {
- prompt = self.c.Views().Confirmation.TextArea.GetContent()
- wrap = false
- }
- panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
- x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
- confirmationViewBottom := y1 - suggestionsViewHeight
- _, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
-
- suggestionsViewTop := confirmationViewBottom + 1
- _, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
-}
-
-func (self *ConfirmationHelper) ResizeCurrentPopupPanel() error {
- c := self.c.CurrentContext()
+func (self *ConfirmationHelper) ResizeCurrentPopupPanels() {
+ var parentPopupContext types.Context
+ for _, c := range self.c.CurrentPopupContexts() {
+ switch c {
+ case self.c.Contexts().Menu:
+ self.resizeMenu(parentPopupContext)
+ case self.c.Contexts().Confirmation, self.c.Contexts().Suggestions:
+ self.resizeConfirmationPanel(parentPopupContext)
+ case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription:
+ self.ResizeCommitMessagePanels(parentPopupContext)
+ }
- switch c {
- case self.c.Contexts().Menu:
- self.resizeMenu()
- case self.c.Contexts().Confirmation, self.c.Contexts().Suggestions:
- self.resizeConfirmationPanel()
- case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription:
- self.ResizeCommitMessagePanels()
+ parentPopupContext = c
}
-
- return nil
-}
-
-func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string) error {
- x0, y0, x1, y1 := self.getPopupPanelDimensions(v.Wrap, content)
- _, err := self.c.GocuiGui().SetView(v.Name(), x0, y0, x1, y1, 0)
- return err
}
-func (self *ConfirmationHelper) resizeMenu() {
+func (self *ConfirmationHelper) resizeMenu(parentPopupContext types.Context) {
// we want the unfiltered length here so that if we're filtering we don't
// resize the window
itemCount := self.c.Contexts().Menu.UnfilteredLen()
offset := 3
panelWidth := self.getPopupPanelWidth()
- x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
+ contentWidth := panelWidth - 2 // minus 2 for the frame
+ promptLinesCount := self.layoutMenuPrompt(contentWidth)
+ x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset+promptLinesCount, parentPopupContext)
menuBottom := y1 - offset
_, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
@@ -368,11 +357,40 @@ func (self *ConfirmationHelper) resizeMenu() {
if selectedItem != nil {
tooltip = self.TooltipForMenuItem(selectedItem)
}
- tooltipHeight := getMessageHeight(true, tooltip, panelWidth) + 2 // plus 2 for the frame
+ tooltipHeight := getMessageHeight(true, tooltip, contentWidth) + 2 // plus 2 for the frame
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
}
-func (self *ConfirmationHelper) resizeConfirmationPanel() {
+// Wraps the lines of the menu prompt to the available width and rerenders the
+// menu if neeeded. Returns the number of lines the prompt takes up.
+func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
+ oldPromptLines := self.c.Contexts().Menu.GetPromptLines()
+ var promptLines []string
+ prompt := self.c.Contexts().Menu.GetPrompt()
+ if len(prompt) > 0 {
+ promptLines = wrapMessageToWidth(true, prompt, contentWidth)
+ promptLines = append(promptLines, "")
+ }
+ self.c.Contexts().Menu.SetPromptLines(promptLines)
+ if len(oldPromptLines) != len(promptLines) {
+ // The number of lines in the prompt has changed; this happens either
+ // because we're now showing a menu that has a prompt, and the previous
+ // menu didn't (or vice versa), or because the user is resizing the
+ // terminal window while a menu with a prompt is open.
+
+ // We need to rerender to give the menu context a chance to update its
+ // non-model items, and reinitialize the data it uses for converting
+ // between view index and model index.
+ _ = self.c.Contexts().Menu.HandleRender()
+
+ // Then we need to refocus to ensure the cursor is in the right place in
+ // the view.
+ _ = self.c.Contexts().Menu.HandleFocus(types.OnFocusOpts{})
+ }
+ return len(promptLines)
+}
+
+func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types.Context) {
suggestionsViewHeight := 0
if self.c.Views().Suggestions.Visible {
suggestionsViewHeight = 11
@@ -385,7 +403,7 @@ func (self *ConfirmationHelper) resizeConfirmationPanel() {
wrap = false
}
panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
- x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
+ x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
confirmationViewBottom := y1 - suggestionsViewHeight
_, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
@@ -393,7 +411,7 @@ func (self *ConfirmationHelper) resizeConfirmationPanel() {
_, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
}
-func (self *ConfirmationHelper) ResizeCommitMessagePanels() {
+func (self *ConfirmationHelper) ResizeCommitMessagePanels(parentPopupContext types.Context) {
panelWidth := self.getPopupPanelWidth()
content := self.c.Views().CommitDescription.TextArea.GetContent()
summaryViewHeight := 3
@@ -402,18 +420,18 @@ func (self *ConfirmationHelper) ResizeCommitMessagePanels() {
if panelHeight < minHeight {
panelHeight = minHeight
}
- x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
+ x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
_, _ = self.c.GocuiGui().SetView(self.c.Views().CommitMessage.Name(), x0, y0, x1, y0+summaryViewHeight-1, 0)
_, _ = self.c.GocuiGui().SetView(self.c.Views().CommitDescription.Name(), x0, y0+summaryViewHeight, x1, y1+summaryViewHeight, 0)
}
-func (self *ConfirmationHelper) IsPopupPanel(viewName string) bool {
- return viewName == "commitMessage" || viewName == "confirmation" || viewName == "menu"
+func (self *ConfirmationHelper) IsPopupPanel(context types.Context) bool {
+ return context.GetKind() == types.PERSISTENT_POPUP || context.GetKind() == types.TEMPORARY_POPUP
}
func (self *ConfirmationHelper) IsPopupPanelFocused() bool {
- return self.IsPopupPanel(self.c.CurrentContext().GetViewName())
+ return self.IsPopupPanel(self.c.CurrentContext())
}
func (self *ConfirmationHelper) TooltipForMenuItem(menuItem *types.MenuItem) string {
diff --git a/pkg/gui/controllers/helpers/credentials_helper.go b/pkg/gui/controllers/helpers/credentials_helper.go
index 20fb5905202..6050c9be8a0 100644
--- a/pkg/gui/controllers/helpers/credentials_helper.go
+++ b/pkg/gui/controllers/helpers/credentials_helper.go
@@ -56,6 +56,8 @@ func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.Credential
return self.c.Tr.CredentialsPassphrase, true
case oscommands.PIN:
return self.c.Tr.CredentialsPIN, true
+ case oscommands.Token:
+ return self.c.Tr.CredentialsToken, true
}
// should never land here
diff --git a/pkg/gui/controllers/helpers/diff_helper.go b/pkg/gui/controllers/helpers/diff_helper.go
index bf4b33052b3..8b5c01cd3e6 100644
--- a/pkg/gui/controllers/helpers/diff_helper.go
+++ b/pkg/gui/controllers/helpers/diff_helper.go
@@ -1,6 +1,7 @@
package helpers
import (
+ "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -119,3 +120,18 @@ func (self *DiffHelper) IgnoringWhitespaceSubTitle() string {
return ""
}
+
+func (self *DiffHelper) OpenDiffToolForRef(selectedRef types.Ref) error {
+ to := selectedRef.RefName()
+ from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff("")
+ _, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj(
+ git_commands.DiffToolCmdOptions{
+ Filepath: ".",
+ FromCommit: from,
+ ToCommit: to,
+ Reverse: reverse,
+ IsDirectory: true,
+ Staged: false,
+ }))
+ return err
+}
diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go
index b60d48f4f92..9cb95140862 100644
--- a/pkg/gui/controllers/helpers/fixup_helper.go
+++ b/pkg/gui/controllers/helpers/fixup_helper.go
@@ -5,13 +5,13 @@ import (
"fmt"
"regexp"
"strings"
- "sync"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
+ "golang.org/x/sync/errgroup"
)
type FixupHelper struct {
@@ -26,7 +26,17 @@ func NewFixupHelper(
}
}
-type deletedLineInfo struct {
+// hunk describes the lines in a diff hunk. Used for two distinct cases:
+//
+// - when the hunk contains some deleted lines. Because we're diffing with a
+// context of 0, all deleted lines always come first, and then the added lines
+// (if any). In this case, numLines is only the number of deleted lines, we
+// ignore whether there are also some added lines in the hunk, as this is not
+// relevant for our algorithm.
+//
+// - when the hunk contains only added lines, in which case (obviously) numLines
+// is the number of added lines.
+type hunk struct {
filename string
startLineIdx int
numLines int
@@ -37,23 +47,74 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
if err != nil {
return err
}
- if diff == "" {
+
+ deletedLineHunks, addedLineHunks := parseDiff(diff)
+
+ commits := self.c.Model().Commits
+
+ var hashes []string
+ warnAboutAddedLines := false
+
+ if len(deletedLineHunks) > 0 {
+ hashes, err = self.blameDeletedLines(deletedLineHunks)
+ warnAboutAddedLines = len(addedLineHunks) > 0
+ } else if len(addedLineHunks) > 0 {
+ hashes, err = self.blameAddedLines(commits, addedLineHunks)
+ } else {
return errors.New(self.c.Tr.NoChangedFiles)
}
- deletedLineInfos, hasHunksWithOnlyAddedLines := self.parseDiff(diff)
- if len(deletedLineInfos) == 0 {
- return errors.New(self.c.Tr.NoDeletedLinesInDiff)
+ if err != nil {
+ return err
}
- hashes := self.blameDeletedLines(deletedLineInfos)
-
if len(hashes) == 0 {
// This should never happen
return errors.New(self.c.Tr.NoBaseCommitsFound)
}
- if len(hashes) > 1 {
- subjects, err := self.c.Git().Commit.GetHashesAndCommitMessagesFirstLine(hashes)
+
+ // If a commit can't be found, and the last known commit is already merged,
+ // we know that the commit we're looking for is also merged. Otherwise we
+ // can't tell.
+ notFoundMeansMerged := len(commits) > 0 && commits[len(commits)-1].Status == models.StatusMerged
+
+ const (
+ MERGED int = iota
+ NOT_MERGED
+ CANNOT_TELL
+ )
+
+ // Group the hashes into buckets by merged status
+ hashGroups := lo.GroupBy(hashes, func(hash string) int {
+ commit, _, ok := self.findCommit(commits, hash)
+ if ok {
+ return lo.Ternary(commit.Status == models.StatusMerged, MERGED, NOT_MERGED)
+ }
+ return lo.Ternary(notFoundMeansMerged, MERGED, CANNOT_TELL)
+ })
+
+ if len(hashGroups[CANNOT_TELL]) > 0 {
+ // If we have any commits that we can't tell if they're merged, just
+ // show the generic "not in current view" error. This can only happen if
+ // a feature branch has more than 300 commits, or there is no main
+ // branch. Both are so unlikely that we don't bother returning a more
+ // detailed error message (e.g. we could say something about the commits
+ // that *are* in the current branch, but it's not worth it).
+ return errors.New(self.c.Tr.BaseCommitIsNotInCurrentView)
+ }
+
+ if len(hashGroups[NOT_MERGED]) == 0 {
+ // If all the commits are merged, show the "already on main branch"
+ // error. It isn't worth doing a detailed report of which commits we
+ // found.
+ return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
+ }
+
+ if len(hashGroups[NOT_MERGED]) > 1 {
+ // If there are multiple commits that could be the base commit, list
+ // them in the error message. But only the candidates from the current
+ // branch, not including any that are already merged.
+ subjects, err := self.c.Git().Commit.GetHashesAndCommitMessagesFirstLine(hashGroups[NOT_MERGED])
if err != nil {
return err
}
@@ -63,23 +124,9 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return fmt.Errorf("%s\n\n%s", message, subjects)
}
- commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
- return commit.Hash == hashes[0]
- })
- if !ok {
- commits := self.c.Model().Commits
- if commits[len(commits)-1].Status == models.StatusMerged {
- // If the commit is not found, it's most likely because it's already
- // merged, and more than 300 commits away. Check if the last known
- // commit is already merged; if so, show the "already merged" error.
- return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
- }
- // If we get here, the current branch must have more then 300 commits. Unlikely...
- return errors.New(self.c.Tr.BaseCommitIsNotInCurrentView)
- }
- if commit.Status == models.StatusMerged {
- return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
- }
+ // At this point we know that the NOT_MERGED bucket has exactly one commit,
+ // and that's the one we want to select.
+ _, index, _ := self.findCommit(commits, hashGroups[NOT_MERGED][0])
doIt := func() error {
if !hasStagedChanges {
@@ -93,7 +140,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return self.c.PushContext(self.c.Contexts().LocalCommits)
}
- if hasHunksWithOnlyAddedLines {
+ if warnAboutAddedLines {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.FindBaseCommitForFixup,
Prompt: self.c.Tr.HunksWithOnlyAddedLinesWarning,
@@ -122,29 +169,38 @@ func (self *FixupHelper) getDiff() (string, bool, error) {
return diff, hasStagedChanges, err
}
-func (self *FixupHelper) parseDiff(diff string) ([]*deletedLineInfo, bool) {
+// Parse the diff output into hunks, and return two lists of hunks: the first
+// are ones that contain deleted lines, the second are ones that contain only
+// added lines.
+func parseDiff(diff string) ([]*hunk, []*hunk) {
lines := strings.Split(strings.TrimSuffix(diff, "\n"), "\n")
- deletedLineInfos := []*deletedLineInfo{}
- hasHunksWithOnlyAddedLines := false
+ deletedLineHunks := []*hunk{}
+ addedLineHunks := []*hunk{}
hunkHeaderRegexp := regexp.MustCompile(`@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@`)
var filename string
- var currentLineInfo *deletedLineInfo
+ var currentHunk *hunk
+ numDeletedLines := 0
+ numAddedLines := 0
finishHunk := func() {
- if currentLineInfo != nil {
- if currentLineInfo.numLines > 0 {
- deletedLineInfos = append(deletedLineInfos, currentLineInfo)
- } else {
- hasHunksWithOnlyAddedLines = true
+ if currentHunk != nil {
+ if numDeletedLines > 0 {
+ currentHunk.numLines = numDeletedLines
+ deletedLineHunks = append(deletedLineHunks, currentHunk)
+ } else if numAddedLines > 0 {
+ currentHunk.numLines = numAddedLines
+ addedLineHunks = append(addedLineHunks, currentHunk)
}
}
+ numDeletedLines = 0
+ numAddedLines = 0
}
for _, line := range lines {
if strings.HasPrefix(line, "diff --git") {
finishHunk()
- currentLineInfo = nil
+ currentHunk = nil
} else if strings.HasPrefix(line, "--- ") {
// For some reason, the line ends with a tab character if the file
// name contains spaces
@@ -153,40 +209,42 @@ func (self *FixupHelper) parseDiff(diff string) ([]*deletedLineInfo, bool) {
finishHunk()
match := hunkHeaderRegexp.FindStringSubmatch(line)
startIdx := utils.MustConvertToInt(match[1])
- currentLineInfo = &deletedLineInfo{filename, startIdx, 0}
- } else if currentLineInfo != nil && line[0] == '-' {
- currentLineInfo.numLines++
+ currentHunk = &hunk{filename, startIdx, 0}
+ } else if currentHunk != nil && line[0] == '-' {
+ numDeletedLines++
+ } else if currentHunk != nil && line[0] == '+' {
+ numAddedLines++
}
}
finishHunk()
- return deletedLineInfos, hasHunksWithOnlyAddedLines
+ return deletedLineHunks, addedLineHunks
}
// returns the list of commit hashes that introduced the lines which have now been deleted
-func (self *FixupHelper) blameDeletedLines(deletedLineInfos []*deletedLineInfo) []string {
- var wg sync.WaitGroup
+func (self *FixupHelper) blameDeletedLines(deletedLineHunks []*hunk) ([]string, error) {
+ errg := errgroup.Group{}
hashChan := make(chan string)
- for _, info := range deletedLineInfos {
- wg.Add(1)
- go func(info *deletedLineInfo) {
- defer wg.Done()
-
- blameOutput, err := self.c.Git().Blame.BlameLineRange(info.filename, "HEAD", info.startLineIdx, info.numLines)
+ for _, h := range deletedLineHunks {
+ errg.Go(func() error {
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx, h.numLines)
if err != nil {
- self.c.Log.Errorf("Error blaming file '%s': %v", info.filename, err)
- return
+ return err
}
blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n")
for _, line := range blameLines {
hashChan <- strings.Split(line, " ")[0]
}
- }(info)
+ return nil
+ })
}
go func() {
- wg.Wait()
+ // We don't care about the error here, we'll check it later (in the
+ // return statement below). Here we only wait for all the goroutines to
+ // finish so that we can close the channel.
+ _ = errg.Wait()
close(hashChan)
}()
@@ -195,5 +253,92 @@ func (self *FixupHelper) blameDeletedLines(deletedLineInfos []*deletedLineInfo)
result.Add(hash)
}
- return result.ToSlice()
+ return result.ToSlice(), errg.Wait()
+}
+
+func (self *FixupHelper) blameAddedLines(commits []*models.Commit, addedLineHunks []*hunk) ([]string, error) {
+ errg := errgroup.Group{}
+ hashesChan := make(chan []string)
+
+ for _, h := range addedLineHunks {
+ errg.Go(func() error {
+ result := make([]string, 0, 2)
+
+ appendBlamedLine := func(blameOutput string) {
+ blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n")
+ if len(blameLines) == 1 {
+ result = append(result, strings.Split(blameLines[0], " ")[0])
+ }
+ }
+
+ // Blame the line before this hunk, if there is one
+ if h.startLineIdx > 0 {
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx, 1)
+ if err != nil {
+ return err
+ }
+ appendBlamedLine(blameOutput)
+ }
+
+ // Blame the line after this hunk. We don't know how many lines the
+ // file has, so we can't check if there is a line after the hunk;
+ // let the error tell us.
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx+1, 1)
+ if err != nil {
+ // If this fails, we're probably at the end of the file (we
+ // could have checked this beforehand, but it's expensive). If
+ // there was a line before this hunk, this is fine, we'll just
+ // return that one; if not, the hunk encompasses the entire
+ // file, and we can't blame the lines before and after the hunk.
+ // This is an error.
+ if h.startLineIdx == 0 {
+ return errors.New("Entire file") // TODO i18n
+ }
+ } else {
+ appendBlamedLine(blameOutput)
+ }
+
+ hashesChan <- result
+ return nil
+ })
+ }
+
+ go func() {
+ // We don't care about the error here, we'll check it later (in the
+ // return statement below). Here we only wait for all the goroutines to
+ // finish so that we can close the channel.
+ _ = errg.Wait()
+ close(hashesChan)
+ }()
+
+ result := set.New[string]()
+ for hashes := range hashesChan {
+ if len(hashes) == 1 {
+ result.Add(hashes[0])
+ } else if len(hashes) > 1 {
+ if hashes[0] == hashes[1] {
+ result.Add(hashes[0])
+ } else {
+ _, index1, ok1 := self.findCommit(commits, hashes[0])
+ _, index2, ok2 := self.findCommit(commits, hashes[1])
+ if ok1 && ok2 {
+ result.Add(lo.Ternary(index1 < index2, hashes[0], hashes[1]))
+ } else if ok1 {
+ result.Add(hashes[0])
+ } else if ok2 {
+ result.Add(hashes[1])
+ } else {
+ return nil, errors.New(self.c.Tr.NoBaseCommitsFound)
+ }
+ }
+ }
+ }
+
+ return result.ToSlice(), errg.Wait()
+}
+
+func (self *FixupHelper) findCommit(commits []*models.Commit, hash string) (*models.Commit, int, bool) {
+ return lo.FindIndexOf(commits, func(commit *models.Commit) bool {
+ return commit.Hash == hash
+ })
}
diff --git a/pkg/gui/controllers/helpers/fixup_helper_test.go b/pkg/gui/controllers/helpers/fixup_helper_test.go
new file mode 100644
index 00000000000..954161cd16a
--- /dev/null
+++ b/pkg/gui/controllers/helpers/fixup_helper_test.go
@@ -0,0 +1,137 @@
+package helpers
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFixupHelper_parseDiff(t *testing.T) {
+ scenarios := []struct {
+ name string
+ diff string
+ expectedDeletedLineHunks []*hunk
+ expectedAddedLineHunks []*hunk
+ }{
+ {
+ name: "no diff",
+ diff: "",
+ expectedDeletedLineHunks: []*hunk{},
+ expectedAddedLineHunks: []*hunk{},
+ },
+ {
+ name: "hunk with only deleted lines",
+ diff: `
+diff --git a/file1.txt b/file1.txt
+index 9ce8efb33..aaf2a4666 100644
+--- a/file1.txt
++++ b/file1.txt
+@@ -3 +2,0 @@ bbb
+-xxx
+`,
+ expectedDeletedLineHunks: []*hunk{
+ {
+ filename: "file1.txt",
+ startLineIdx: 3,
+ numLines: 1,
+ },
+ },
+ expectedAddedLineHunks: []*hunk{},
+ },
+ {
+ name: "hunk with deleted and added lines",
+ diff: `
+diff --git a/file1.txt b/file1.txt
+index 9ce8efb33..eb246cf98 100644
+--- a/file1.txt
++++ b/file1.txt
+@@ -3 +3 @@ bbb
+-xxx
++yyy
+`,
+ expectedDeletedLineHunks: []*hunk{
+ {
+ filename: "file1.txt",
+ startLineIdx: 3,
+ numLines: 1,
+ },
+ },
+ expectedAddedLineHunks: []*hunk{},
+ },
+ {
+ name: "hunk with only added lines",
+ diff: `
+diff --git a/file1.txt b/file1.txt
+index 9ce8efb33..fb5e469e7 100644
+--- a/file1.txt
++++ b/file1.txt
+@@ -4,0 +5,2 @@ ddd
++xxx
++yyy
+`,
+ expectedDeletedLineHunks: []*hunk{},
+ expectedAddedLineHunks: []*hunk{
+ {
+ filename: "file1.txt",
+ startLineIdx: 4,
+ numLines: 2,
+ },
+ },
+ },
+ {
+ name: "several hunks in different files",
+ diff: `
+diff --git a/file1.txt b/file1.txt
+index 9ce8efb33..0632e41b0 100644
+--- a/file1.txt
++++ b/file1.txt
+@@ -2 +1,0 @@ aaa
+-bbb
+@@ -4 +3 @@ ccc
+-ddd
++xxx
+@@ -6,0 +6 @@ fff
++zzz
+diff --git a/file2.txt b/file2.txt
+index 9ce8efb33..0632e41b0 100644
+--- a/file2.txt
++++ b/file2.txt
+@@ -0,3 +1,0 @@ aaa
+-aaa
+-bbb
+-ccc
+`,
+ expectedDeletedLineHunks: []*hunk{
+ {
+ filename: "file1.txt",
+ startLineIdx: 2,
+ numLines: 1,
+ },
+ {
+ filename: "file1.txt",
+ startLineIdx: 4,
+ numLines: 1,
+ },
+ {
+ filename: "file2.txt",
+ startLineIdx: 0,
+ numLines: 3,
+ },
+ },
+ expectedAddedLineHunks: []*hunk{
+ {
+ filename: "file1.txt",
+ startLineIdx: 6,
+ numLines: 1,
+ },
+ },
+ },
+ }
+ for _, s := range scenarios {
+ t.Run(s.name, func(t *testing.T) {
+ deletedLineHunks, addedLineHunks := parseDiff(s.diff)
+ assert.Equal(t, s.expectedDeletedLineHunks, deletedLineHunks)
+ assert.Equal(t, s.expectedAddedLineHunks, addedLineHunks)
+ })
+ }
+}
diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
index 4bffcfa99ee..a5554aa5870 100644
--- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
+++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
@@ -9,10 +9,12 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
+ "github.com/stefanhaller/git-todo-parser/todo"
)
type MergeAndRebaseHelper struct {
@@ -110,7 +112,12 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
// we should end up with a command like 'git merge --continue'
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
- if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig.Git.Merging.ManualCommit {
+ needsSubprocess := (status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig.Git.Merging.ManualCommit) ||
+ // but we'll also use a subprocess if we have exec todos; those are likely to be lengthy build
+ // tasks whose output the user will want to see in the terminal
+ (status == enums.REBASE_MODE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos())
+
+ if needsSubprocess {
// TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction
return self.c.RunSubprocessAndRefresh(
self.c.Git().Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
@@ -123,6 +130,18 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
return nil
}
+func (self *MergeAndRebaseHelper) hasExecTodos() bool {
+ for _, commit := range self.c.Model().Commits {
+ if commit.Status != models.StatusRebasing {
+ break
+ }
+ if commit.Action == todo.Exec {
+ return true
+ }
+ }
+ return false
+}
+
var conflictStrings = []string{
"Failed to merge in the changes",
"When you have resolved this problem",
@@ -234,11 +253,29 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
}
func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
- checkedOutBranch := self.refsHelper.GetCheckedOutRef().Name
+ checkedOutBranch := self.refsHelper.GetCheckedOutRef()
+ checkedOutBranchName := self.refsHelper.GetCheckedOutRef().Name
+ var disabledReason, baseBranchDisabledReason *types.DisabledReason
+ if checkedOutBranchName == ref {
+ disabledReason = &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
+ }
+
+ baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(checkedOutBranch, self.refsHelper.c.Model().MainBranches)
+ if err != nil {
+ return err
+ }
+ if baseBranch == "" {
+ baseBranch = self.c.Tr.CouldNotDetermineBaseBranch
+ baseBranchDisabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch}
+ }
+
menuItems := []*types.MenuItem{
{
- Label: self.c.Tr.SimpleRebase,
- Key: 's',
+ Label: utils.ResolvePlaceholderString(self.c.Tr.SimpleRebase,
+ map[string]string{"ref": ref},
+ ),
+ Key: 's',
+ DisabledReason: disabledReason,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
@@ -258,9 +295,12 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
},
},
{
- Label: self.c.Tr.InteractiveRebase,
- Key: 'i',
- Tooltip: self.c.Tr.InteractiveRebaseTooltip,
+ Label: utils.ResolvePlaceholderString(self.c.Tr.InteractiveRebase,
+ map[string]string{"ref": ref},
+ ),
+ Key: 'i',
+ DisabledReason: disabledReason,
+ Tooltip: self.c.Tr.InteractiveRebaseTooltip,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
@@ -279,6 +319,31 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
return self.c.PushContext(self.c.Contexts().LocalCommits)
},
},
+ {
+ Label: utils.ResolvePlaceholderString(self.c.Tr.RebaseOntoBaseBranch,
+ map[string]string{"baseBranch": ShortBranchName(baseBranch)},
+ ),
+ Key: 'b',
+ DisabledReason: baseBranchDisabledReason,
+ Tooltip: self.c.Tr.RebaseOntoBaseBranchTooltip,
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
+ return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
+ baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
+ var err error
+ if baseCommit != "" {
+ err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(baseBranch, baseCommit)
+ } else {
+ err = self.c.Git().Rebase.RebaseBranch(baseBranch)
+ }
+ err = self.CheckMergeOrRebase(err)
+ if err == nil {
+ return self.ResetMarkedBaseCommit()
+ }
+ return err
+ })
+ },
+ },
}
title := utils.ResolvePlaceholderString(
@@ -286,8 +351,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
self.c.Tr.RebasingFromBaseCommitTitle,
self.c.Tr.RebasingTitle),
map[string]string{
- "checkedOutBranch": checkedOutBranch,
- "ref": ref,
+ "checkedOutBranch": checkedOutBranchName,
},
)
@@ -305,25 +369,84 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
if checkedOutBranchName == refName {
return errors.New(self.c.Tr.CantMergeBranchIntoItself)
}
- prompt := utils.ResolvePlaceholderString(
- self.c.Tr.ConfirmMerge,
- map[string]string{
- "checkedOutBranch": checkedOutBranchName,
- "selectedBranch": refName,
- },
- )
- return self.c.Confirm(types.ConfirmOpts{
- Title: self.c.Tr.MergeConfirmTitle,
- Prompt: prompt,
- HandleConfirm: func() error {
- self.c.LogAction(self.c.Tr.Actions.Merge)
- err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{})
- return self.CheckMergeOrRebase(err)
+ return self.c.Menu(types.CreateMenuOptions{
+ Title: self.c.Tr.Merge,
+ Items: []*types.MenuItem{
+ {
+ Label: self.c.Tr.RegularMerge,
+ OnPress: self.RegularMerge(refName),
+ Key: 'm',
+ Tooltip: utils.ResolvePlaceholderString(
+ self.c.Tr.RegularMergeTooltip,
+ map[string]string{
+ "checkedOutBranch": checkedOutBranchName,
+ "selectedBranch": refName,
+ },
+ ),
+ },
+ {
+ Label: self.c.Tr.SquashMergeUncommittedTitle,
+ OnPress: self.SquashMergeUncommitted(refName),
+ Key: 's',
+ Tooltip: utils.ResolvePlaceholderString(
+ self.c.Tr.SquashMergeUncommitted,
+ map[string]string{
+ "selectedBranch": refName,
+ },
+ ),
+ },
+ {
+ Label: self.c.Tr.SquashMergeCommittedTitle,
+ OnPress: self.SquashMergeCommitted(refName, checkedOutBranchName),
+ Key: 'S',
+ Tooltip: utils.ResolvePlaceholderString(
+ self.c.Tr.SquashMergeCommitted,
+ map[string]string{
+ "checkedOutBranch": checkedOutBranchName,
+ "selectedBranch": refName,
+ },
+ ),
+ },
},
})
}
+func (self *MergeAndRebaseHelper) RegularMerge(refName string) func() error {
+ return func() error {
+ self.c.LogAction(self.c.Tr.Actions.Merge)
+ err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{})
+ return self.CheckMergeOrRebase(err)
+ }
+}
+
+func (self *MergeAndRebaseHelper) SquashMergeUncommitted(refName string) func() error {
+ return func() error {
+ self.c.LogAction(self.c.Tr.Actions.SquashMerge)
+ err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{Squash: true})
+ return self.CheckMergeOrRebase(err)
+ }
+}
+
+func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranchName string) func() error {
+ return func() error {
+ self.c.LogAction(self.c.Tr.Actions.SquashMerge)
+ err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{Squash: true})
+ if err = self.CheckMergeOrRebase(err); err != nil {
+ return err
+ }
+ message := utils.ResolvePlaceholderString(self.c.UserConfig.Git.Merging.SquashMergeMessage, map[string]string{
+ "selectedRef": refName,
+ "currentBranch": checkedOutBranchName,
+ })
+ err = self.c.Git().Commit.CommitCmdObj(message, "").Run()
+ if err != nil {
+ return err
+ }
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
+ }
+}
+
func (self *MergeAndRebaseHelper) ResetMarkedBaseCommit() error {
self.c.Modes().MarkedBaseCommit.Reset()
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go
index d8f83255d7a..dd4c3515af0 100644
--- a/pkg/gui/controllers/helpers/patch_building_helper.go
+++ b/pkg/gui/controllers/helpers/patch_building_helper.go
@@ -3,6 +3,7 @@ package helpers
import (
"errors"
+ "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -80,7 +81,12 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
return err
}
- secondaryDiff := self.c.Git().Patch.PatchBuilder.RenderPatchForFile(path, false, false)
+ secondaryDiff := self.c.Git().Patch.PatchBuilder.RenderPatchForFile(patch.RenderPatchForFileOpts{
+ Filename: path,
+ Plain: false,
+ Reverse: false,
+ TurnAddedFilesIntoDiffAgainstEmptyFile: true,
+ })
context := self.c.Contexts().CustomPatchBuilder
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index 8d88faeec2a..21c20c6468d 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -127,7 +127,12 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
refresh("commits and commit files", self.refreshCommitsAndCommitFiles)
includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES)
- refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) })
+ if self.c.AppState.LocalBranchSortOrder == "recency" {
+ refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) })
+ } else {
+ refresh("branches", func() { self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex, true) })
+ refresh("reflog", func() { _ = self.refreshReflogCommits() })
+ }
} else if scopeSet.Includes(types.REBASE_COMMITS) {
// the above block handles rebase commits so we only need to call this one
// if we've asked specifically for rebase commits and not those other things
@@ -251,7 +256,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
case types.INITIAL:
self.c.OnWorker(func(_ gocui.Task) error {
_ = self.refreshReflogCommits()
- self.refreshBranches(false, true)
+ self.refreshBranches(false, true, true)
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
return nil
})
@@ -262,9 +267,11 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
}
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
+ loadBehindCounts := self.c.State().GetRepoState().GetStartupStage() == types.COMPLETE
+
self.refreshReflogCommitsConsideringStartup()
- self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex)
+ self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex, loadBehindCounts)
}
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
@@ -326,6 +333,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
RefName: self.refForLog(),
RefForPushedStatus: checkedOutBranchName,
All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
+ MainBranches: self.c.Model().MainBranches,
},
)
if err != nil {
@@ -352,6 +360,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(),
RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(),
RefForPushedStatus: self.c.Contexts().SubCommits.GetRef().FullRefName(),
+ MainBranches: self.c.Model().MainBranches,
},
)
if err != nil {
@@ -431,7 +440,7 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
// self.refreshStatus is called at the end of this because that's when we can
// be sure there is a State.Model.Branches array to pick the current branch from
-func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
+func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool, loadBehindCounts bool) {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
@@ -450,7 +459,25 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
}
}
- branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
+ branches, err := self.c.Git().Loaders.BranchLoader.Load(
+ reflogCommits,
+ self.c.Model().MainBranches,
+ self.c.Model().Branches,
+ loadBehindCounts,
+ func(f func() error) {
+ self.c.OnWorker(func(_ gocui.Task) error {
+ return f()
+ })
+ },
+ func() {
+ self.c.OnUIThread(func() error {
+ if err := self.c.Contexts().Branches.HandleRender(); err != nil {
+ self.c.Log.Error(err)
+ }
+ self.refreshStatus()
+ return nil
+ })
+ })
if err != nil {
self.c.Log.Error(err)
}
@@ -732,6 +759,14 @@ func (self *RefreshHelper) refForLog() string {
}
func (self *RefreshHelper) refreshView(context types.Context) error {
+ // Re-applying the filter must be done before re-rendering the view, so that
+ // the filtered list model is up to date for rendering.
self.searchHelper.ReApplyFilter(context)
- return self.c.PostRefreshUpdate(context)
+
+ err := self.c.PostRefreshUpdate(context)
+
+ // Re-applying the search must be done after re-rendering the view though,
+ // so that the "x of y" status is shown correctly.
+ self.searchHelper.ReApplySearch(context)
+ return err
}
diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go
index d837d826647..f471f57e2fc 100644
--- a/pkg/gui/controllers/helpers/refs_helper.go
+++ b/pkg/gui/controllers/helpers/refs_helper.go
@@ -130,6 +130,7 @@ func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchN
Title: utils.ResolvePlaceholderString(self.c.Tr.RemoteBranchCheckoutTitle, map[string]string{
"branchName": fullBranchName,
}),
+ Prompt: self.c.Tr.RemoteBranchCheckoutPrompt,
Items: []*types.MenuItem{
{
Label: self.c.Tr.CheckoutTypeNewBranch,
@@ -189,7 +190,7 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
return nil
}
-func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, onSelected func(sortOrder string) error) error {
+func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, onSelected func(sortOrder string) error, currentValue string) error {
type sortMenuOption struct {
key types.Key
label string
@@ -220,7 +221,8 @@ func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, onSelecte
OnPress: func() error {
return onSelected(opt.sortOrder)
},
- Key: opt.key,
+ Key: opt.key,
+ Widget: types.MakeMenuRadioButton(opt.sortOrder == currentValue),
}
})
return self.c.Menu(types.CreateMenuOptions{
@@ -272,12 +274,21 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
},
)
+ if suggestedBranchName == "" {
+ suggestedBranchName = self.c.UserConfig.Git.BranchPrefix
+ }
+
return self.c.Prompt(types.PromptOpts{
Title: message,
InitialContent: suggestedBranchName,
HandleConfirm: func(response string) error {
self.c.LogAction(self.c.Tr.Actions.CreateBranch)
- if err := self.c.Git().Branch.New(SanitizedBranchName(response), from); err != nil {
+ newBranchName := SanitizedBranchName(response)
+ newBranchFunc := self.c.Git().Branch.New
+ if newBranchName != suggestedBranchName {
+ newBranchFunc = self.c.Git().Branch.NewWithoutTracking
+ }
+ if err := newBranchFunc(newBranchName, from); err != nil {
return err
}
diff --git a/pkg/gui/controllers/helpers/search_helper.go b/pkg/gui/controllers/helpers/search_helper.go
index c317209499c..653640e0209 100644
--- a/pkg/gui/controllers/helpers/search_helper.go
+++ b/pkg/gui/controllers/helpers/search_helper.go
@@ -2,12 +2,14 @@ package helpers
import (
"fmt"
+ "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
+ "github.com/jesseduffield/lazygit/pkg/utils"
)
// NOTE: this helper supports both filtering and searching. Filtering is when
@@ -156,17 +158,26 @@ func (self *SearchHelper) ConfirmSearch() error {
context.GetSearchHistory().Push(searchString)
}
- view := context.GetView()
-
if err := self.c.PopContext(); err != nil {
return err
}
- if err := view.Search(searchString); err != nil {
- return err
+ return context.GetView().Search(searchString, modelSearchResults(context))
+}
+
+func modelSearchResults(context types.ISearchableContext) []gocui.SearchPosition {
+ searchString := context.GetSearchString()
+
+ var normalizedSearchStr string
+ // if we have any uppercase characters we'll do a case-sensitive search
+ caseSensitive := utils.ContainsUppercase(searchString)
+ if caseSensitive {
+ normalizedSearchStr = searchString
+ } else {
+ normalizedSearchStr = strings.ToLower(searchString)
}
- return nil
+ return context.ModelSearchResults(normalizedSearchStr, caseSensitive)
}
func (self *SearchHelper) CancelPrompt() error {
@@ -228,13 +239,32 @@ func (self *SearchHelper) OnPromptContentChanged(searchString string) {
}
func (self *SearchHelper) ReApplyFilter(context types.Context) {
- state := self.searchState()
- if context == state.Context {
- filterableContext, ok := context.(types.IFilterableContext)
- if ok {
+ filterableContext, ok := context.(types.IFilterableContext)
+ if ok {
+ state := self.searchState()
+ if context == state.Context {
filterableContext.SetSelection(0)
_ = filterableContext.GetView().SetOriginY(0)
- filterableContext.ReApplyFilter(self.c.UserConfig.Gui.UseFuzzySearch())
+ }
+ filterableContext.ReApplyFilter(self.c.UserConfig.Gui.UseFuzzySearch())
+ }
+}
+
+func (self *SearchHelper) ReApplySearch(ctx types.Context) {
+ // Reapply the search if the model has changed. This is needed for contexts
+ // that use the model for searching, to pass the new model search positions
+ // to the view.
+ searchableContext, ok := ctx.(types.ISearchableContext)
+ if ok {
+ ctx.GetView().UpdateSearchResults(searchableContext.GetSearchString(), modelSearchResults(searchableContext))
+
+ state := self.searchState()
+ if ctx == state.Context {
+ // Re-render the "x of y" search status, unless the search prompt is
+ // open for typing.
+ if self.c.CurrentContext().GetKey() != context.SEARCH_CONTEXT_KEY {
+ self.RenderSearchStatus(searchableContext)
+ }
}
}
}
diff --git a/pkg/gui/controllers/helpers/sub_commits_helper.go b/pkg/gui/controllers/helpers/sub_commits_helper.go
index c31d5093709..f1cecf7f579 100644
--- a/pkg/gui/controllers/helpers/sub_commits_helper.go
+++ b/pkg/gui/controllers/helpers/sub_commits_helper.go
@@ -44,6 +44,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
RefName: opts.Ref.FullRefName(),
RefForPushedStatus: opts.Ref.FullRefName(),
RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom,
+ MainBranches: self.c.Model().MainBranches,
},
)
if err != nil {
diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper.go b/pkg/gui/controllers/helpers/window_arrangement_helper.go
index 8615769dc1b..322cd1bd646 100644
--- a/pkg/gui/controllers/helpers/window_arrangement_helper.go
+++ b/pkg/gui/controllers/helpers/window_arrangement_helper.go
@@ -8,7 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
- "github.com/mattn/go-runewidth"
"golang.org/x/exp/slices"
)
@@ -216,6 +215,15 @@ func mainSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
}
}
+ if args.CurrentWindow == "secondary" && args.ScreenMode == types.SCREEN_FULL {
+ return []*boxlayout.Box{
+ {
+ Window: "secondary",
+ Weight: 1,
+ },
+ }
+ }
+
return []*boxlayout.Box{
{
Window: "main",
@@ -239,7 +247,7 @@ func getMidSectionWeights(args WindowArrangementArgs) (int, int) {
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
}
- if args.CurrentWindow == "main" {
+ if args.CurrentWindow == "main" || args.CurrentWindow == "secondary" {
if args.ScreenMode == types.SCREEN_HALF || args.ScreenMode == types.SCREEN_FULL {
sideSectionWeight = 0
}
@@ -263,7 +271,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
return []*boxlayout.Box{
{
Window: "searchPrefix",
- Size: runewidth.StringWidth(args.SearchPrefix),
+ Size: utils.StringWidth(args.SearchPrefix),
},
{
Window: "search",
@@ -316,7 +324,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
// app status appears very briefly in demos and dislodges the caption,
// so better not to show it at all
if args.AppStatus != "" {
- result = append(result, &boxlayout.Box{Window: "appStatus", Size: runewidth.StringWidth(args.AppStatus)})
+ result = append(result, &boxlayout.Box{Window: "appStatus", Size: utils.StringWidth(args.AppStatus)})
}
}
@@ -329,7 +337,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
&boxlayout.Box{
Window: "information",
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
- Size: runewidth.StringWidth(utils.Decolorise(args.InformationStr)),
+ Size: utils.StringWidth(utils.Decolorise(args.InformationStr)),
})
}
@@ -443,7 +451,7 @@ func sidePanelChildren(args WindowArrangementArgs) func(width int, height int) [
if accordionMode && defaultBox.Window == args.CurrentSideWindow {
return &boxlayout.Box{
Window: defaultBox.Window,
- Weight: 2,
+ Weight: args.UserConfig.Gui.ExpandedSidePanelWeight,
}
}
diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go
index 9c455ff331d..b429e00ae42 100644
--- a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go
+++ b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go
@@ -121,6 +121,87 @@ func TestGetWindowDimensions(t *testing.T) {
B: information
`,
},
+ {
+ name: "expandFocusedSidePanel",
+ mutateArgs: func(args *WindowArrangementArgs) {
+ args.UserConfig.Gui.ExpandFocusedSidePanel = true
+ },
+ expected: `
+ ╭status─────────────────╮╭main────────────────────────────────────────────╮
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭files──────────────────╮│ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭branches───────────────╮│ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭commits────────────────╮│ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭stash──────────────────╮│ │
+ │ ││ │
+ ╰───────────────────────╯╰────────────────────────────────────────────────╯
+ A
+ A: statusSpacer1
+ B: information
+ `,
+ },
+ {
+ name: "expandSidePanelWeight",
+ mutateArgs: func(args *WindowArrangementArgs) {
+ args.UserConfig.Gui.ExpandFocusedSidePanel = true
+ args.UserConfig.Gui.ExpandedSidePanelWeight = 4
+ },
+ expected: `
+ ╭status─────────────────╮╭main────────────────────────────────────────────╮
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭files──────────────────╮│ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭branches───────────────╮│ │
+ │ ││ │
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭commits────────────────╮│ │
+ │ ││ │
+ │ ││ │
+ ╰───────────────────────╯│ │
+ ╭stash──────────────────╮│ │
+ │ ││ │
+ ╰───────────────────────╯╰────────────────────────────────────────────────╯
+ A
+ A: statusSpacer1
+ B: information
+ `,
+ },
{
name: "half screen mode, enlargedSideViewLocation left",
mutateArgs: func(args *WindowArrangementArgs) {
diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go
index a97639795d2..51a6bc553a9 100644
--- a/pkg/gui/controllers/helpers/working_tree_helper.go
+++ b/pkg/gui/controllers/helpers/working_tree_helper.go
@@ -152,12 +152,16 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
if commitPrefixConfig != nil {
prefixPattern := commitPrefixConfig.Pattern
prefixReplace := commitPrefixConfig.Replace
+ branchName := self.refHelper.GetCheckedOutRef().Name
rgx, err := regexp.Compile(prefixPattern)
if err != nil {
return fmt.Errorf("%s: %s", self.c.Tr.CommitPrefixPatternError, err.Error())
}
- prefix := rgx.ReplaceAllString(self.refHelper.GetCheckedOutRef().Name, prefixReplace)
- message = prefix
+
+ if rgx.MatchString(branchName) {
+ prefix := rgx.ReplaceAllString(branchName, prefixReplace)
+ message = prefix
+ }
}
}
diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go
index dc876b3fca3..711d32f79f2 100644
--- a/pkg/gui/controllers/list_controller.go
+++ b/pkg/gui/controllers/list_controller.go
@@ -53,6 +53,9 @@ func (self *ListController) HandleScrollRight() error {
func (self *ListController) HandleScrollUp() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
self.context.GetViewTrait().ScrollUp(scrollHeight)
+ if self.context.RenderOnlyVisibleLines() {
+ return self.context.HandleRender()
+ }
return nil
}
@@ -60,6 +63,9 @@ func (self *ListController) HandleScrollUp() error {
func (self *ListController) HandleScrollDown() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight)
+ if self.context.RenderOnlyVisibleLines() {
+ return self.context.HandleRender()
+ }
return nil
}
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index 53a6b205f29..f95062fdbe3 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -238,8 +238,8 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
},
{
Key: opts.GetKey(opts.Config.Commits.ResetCommitAuthor),
- Handler: self.withItem(self.amendAttribute),
- GetDisabledReason: self.require(self.singleItemSelected(self.canAmend)),
+ Handler: self.withItemsRange(self.amendAttribute),
+ GetDisabledReason: self.require(self.itemRangeSelected(self.canAmendRange)),
Description: self.c.Tr.AmendCommitAttribute,
Tooltip: self.c.Tr.AmendCommitAttributeTooltip,
OpensMenu: true,
@@ -284,6 +284,9 @@ func (self *LocalCommitsController) GetOnRenderToMain() func() error {
map[string]string{
"ref": strings.TrimPrefix(commit.Name, "refs/heads/"),
}))
+ } else if commit.Action == todo.Exec {
+ task = types.NewRenderStringTask(
+ self.c.Tr.ExecCommandHere + "\n\n" + commit.Name)
} else {
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Hash, self.c.Modes().Filtering.GetPath())
task = types.NewRunPtyTask(cmdObj.GetCmd())
@@ -371,7 +374,7 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
}
func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepath string) error {
- if self.isHeadCommit() {
+ if self.isSelectedHeadCommit() {
return self.c.RunSubprocessAndRefresh(
self.c.Git().Commit.RewordLastCommitInEditorWithMessageFileCmdObj(filepath))
}
@@ -408,7 +411,7 @@ func (self *LocalCommitsController) handleReword(summary string, description str
func (self *LocalCommitsController) doRewordEditor() error {
self.c.LogAction(self.c.Tr.Actions.RewordCommit)
- if self.isHeadCommit() {
+ if self.isSelectedHeadCommit() {
return self.c.RunSubprocessAndRefresh(self.c.Git().Commit.RewordLastCommitInEditorCmdObj())
}
@@ -607,7 +610,7 @@ func (self *LocalCommitsController) rewordEnabled(commit *models.Commit) *types.
// If we are in a rebase, the only action that is allowed for
// non-todo commits is rewording the current head commit
- if self.isRebasing() && !self.isHeadCommit() {
+ if self.isRebasing() && !self.isSelectedHeadCommit() {
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
@@ -665,7 +668,7 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
}
func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
- if self.isHeadCommit() {
+ if self.isSelectedHeadCommit() {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.AmendCommitTitle,
Prompt: self.c.Tr.AmendCommitPrompt,
@@ -695,34 +698,39 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
})
}
-func (self *LocalCommitsController) canAmend(commit *models.Commit) *types.DisabledReason {
- if !self.isHeadCommit() && self.isRebasing() {
+func (self *LocalCommitsController) canAmendRange(commits []*models.Commit, start, end int) *types.DisabledReason {
+ if (start != end || !self.isHeadCommit(start)) && self.isRebasing() {
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
return nil
}
-func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error {
+func (self *LocalCommitsController) canAmend(_ *models.Commit) *types.DisabledReason {
+ idx := self.context().GetSelectedLineIdx()
+ return self.canAmendRange(self.c.Model().Commits, idx, idx)
+}
+
+func (self *LocalCommitsController) amendAttribute(commits []*models.Commit, start, end int) error {
opts := self.c.KeybindingsOpts()
return self.c.Menu(types.CreateMenuOptions{
Title: "Amend commit attribute",
Items: []*types.MenuItem{
{
Label: self.c.Tr.ResetAuthor,
- OnPress: self.resetAuthor,
+ OnPress: func() error { return self.resetAuthor(start, end) },
Key: opts.GetKey(opts.Config.AmendAttribute.ResetAuthor),
Tooltip: self.c.Tr.ResetAuthorTooltip,
},
{
Label: self.c.Tr.SetAuthor,
- OnPress: self.setAuthor,
+ OnPress: func() error { return self.setAuthor(start, end) },
Key: opts.GetKey(opts.Config.AmendAttribute.SetAuthor),
Tooltip: self.c.Tr.SetAuthorTooltip,
},
{
Label: self.c.Tr.AddCoAuthor,
- OnPress: self.addCoAuthor,
+ OnPress: func() error { return self.addCoAuthor(start, end) },
Key: opts.GetKey(opts.Config.AmendAttribute.AddCoAuthor),
Tooltip: self.c.Tr.AddCoAuthorTooltip,
},
@@ -730,10 +738,10 @@ func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error
})
}
-func (self *LocalCommitsController) resetAuthor() error {
+func (self *LocalCommitsController) resetAuthor(start, end int) error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
- if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil {
+ if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, start, end); err != nil {
return err
}
@@ -741,14 +749,14 @@ func (self *LocalCommitsController) resetAuthor() error {
})
}
-func (self *LocalCommitsController) setAuthor() error {
+func (self *LocalCommitsController) setAuthor(start, end int) error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.SetAuthorPromptTitle,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
HandleConfirm: func(value string) error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
- if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
+ if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, start, end, value); err != nil {
return err
}
@@ -758,14 +766,14 @@ func (self *LocalCommitsController) setAuthor() error {
})
}
-func (self *LocalCommitsController) addCoAuthor() error {
+func (self *LocalCommitsController) addCoAuthor(start, end int) error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.AddCoAuthorPromptTitle,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
HandleConfirm: func(value string) error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.AddCommitCoAuthor)
- if err := self.c.Git().Rebase.AddCommitCoAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
+ if err := self.c.Git().Rebase.AddCommitCoAuthor(self.c.Model().Commits, start, end, value); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -1077,6 +1085,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
Label: self.c.Tr.ShowGitGraph,
OpensMenu: true,
OnPress: func() error {
+ currentValue := self.c.GetAppState().GitLogShowGraph
onPress := func(value string) func() error {
return func() error {
self.c.GetAppState().GitLogShowGraph = value
@@ -1093,14 +1102,17 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
{
Label: "always",
OnPress: onPress("always"),
+ Widget: types.MakeMenuRadioButton(currentValue == "always"),
},
{
Label: "never",
OnPress: onPress("never"),
+ Widget: types.MakeMenuRadioButton(currentValue == "never"),
},
{
Label: "when maximised",
OnPress: onPress("when-maximised"),
+ Widget: types.MakeMenuRadioButton(currentValue == "when-maximised"),
},
},
})
@@ -1110,6 +1122,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
Label: self.c.Tr.SortCommits,
OpensMenu: true,
OnPress: func() error {
+ currentValue := self.c.GetAppState().GitLogOrder
onPress := func(value string) func() error {
return func() error {
self.c.GetAppState().GitLogOrder = value
@@ -1131,14 +1144,17 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
{
Label: "topological (topo-order)",
OnPress: onPress("topo-order"),
+ Widget: types.MakeMenuRadioButton(currentValue == "topo-order"),
},
{
Label: "date-order",
OnPress: onPress("date-order"),
+ Widget: types.MakeMenuRadioButton(currentValue == "date-order"),
},
{
Label: "author-date-order",
OnPress: onPress("author-date-order"),
+ Widget: types.MakeMenuRadioButton(currentValue == "author-date-order"),
},
},
})
@@ -1188,8 +1204,12 @@ func (self *LocalCommitsController) markAsBaseCommit(commit *models.Commit) erro
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
-func (self *LocalCommitsController) isHeadCommit() bool {
- return models.IsHeadCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx())
+func (self *LocalCommitsController) isHeadCommit(idx int) bool {
+ return models.IsHeadCommit(self.c.Model().Commits, idx)
+}
+
+func (self *LocalCommitsController) isSelectedHeadCommit() bool {
+ return self.isHeadCommit(self.context().GetSelectedLineIdx())
}
func (self *LocalCommitsController) notMidRebase(message string) func() *types.DisabledReason {
diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go
index 97dbf56b00e..c859ef3f63a 100644
--- a/pkg/gui/controllers/remote_branches_controller.go
+++ b/pkg/gui/controllers/remote_branches_controller.go
@@ -94,6 +94,14 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
Tooltip: self.c.Tr.ResetTooltip,
OpensMenu: true,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
+ Handler: self.withItem(func(selectedBranch *models.RemoteBranch) error {
+ return self.c.Helpers().Diff.OpenDiffToolForRef(selectedBranch)
+ }),
+ GetDisabledReason: self.require(self.singleItemSelected()),
+ Description: self.c.Tr.OpenDiffTool,
+ },
}
}
@@ -145,7 +153,8 @@ func (self *RemoteBranchesController) createSortMenu() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.REMOTES}})
}
return nil
- })
+ },
+ self.c.GetAppState().RemoteBranchSortOrder)
}
func (self *RemoteBranchesController) createResetMenu(selectedBranch *models.RemoteBranch) error {
diff --git a/pkg/gui/controllers/rename_similarity_threshold_controller.go b/pkg/gui/controllers/rename_similarity_threshold_controller.go
new file mode 100644
index 00000000000..0b154aa3665
--- /dev/null
+++ b/pkg/gui/controllers/rename_similarity_threshold_controller.go
@@ -0,0 +1,100 @@
+package controllers
+
+import (
+ "fmt"
+
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/samber/lo"
+)
+
+// This controller lets you change the similarity threshold for detecting renames.
+
+var CONTEXT_KEYS_SHOWING_RENAMES = []types.ContextKey{
+ context.FILES_CONTEXT_KEY,
+ context.SUB_COMMITS_CONTEXT_KEY,
+ context.LOCAL_COMMITS_CONTEXT_KEY,
+ context.STASH_CONTEXT_KEY,
+}
+
+type RenameSimilarityThresholdController struct {
+ baseController
+ c *ControllerCommon
+}
+
+var _ types.IController = &RenameSimilarityThresholdController{}
+
+func NewRenameSimilarityThresholdController(
+ common *ControllerCommon,
+) *RenameSimilarityThresholdController {
+ return &RenameSimilarityThresholdController{
+ baseController: baseController{},
+ c: common,
+ }
+}
+
+func (self *RenameSimilarityThresholdController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: opts.GetKey(opts.Config.Universal.IncreaseRenameSimilarityThreshold),
+ Handler: self.Increase,
+ Description: self.c.Tr.IncreaseRenameSimilarityThreshold,
+ Tooltip: self.c.Tr.IncreaseRenameSimilarityThresholdTooltip,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.DecreaseRenameSimilarityThreshold),
+ Handler: self.Decrease,
+ Description: self.c.Tr.DecreaseRenameSimilarityThreshold,
+ Tooltip: self.c.Tr.DecreaseRenameSimilarityThresholdTooltip,
+ },
+ }
+
+ return bindings
+}
+
+func (self *RenameSimilarityThresholdController) Context() types.Context {
+ return nil
+}
+
+func (self *RenameSimilarityThresholdController) Increase() error {
+ old_size := self.c.AppState.RenameSimilarityThreshold
+
+ if self.isShowingRenames() && old_size < 100 {
+ self.c.AppState.RenameSimilarityThreshold = min(100, old_size+5)
+ return self.applyChange()
+ }
+
+ return nil
+}
+
+func (self *RenameSimilarityThresholdController) Decrease() error {
+ old_size := self.c.AppState.RenameSimilarityThreshold
+
+ if self.isShowingRenames() && old_size > 5 {
+ self.c.AppState.RenameSimilarityThreshold = max(5, old_size-5)
+ return self.applyChange()
+ }
+
+ return nil
+}
+
+func (self *RenameSimilarityThresholdController) applyChange() error {
+ self.c.Toast(fmt.Sprintf(self.c.Tr.RenameSimilarityThresholdChanged, self.c.AppState.RenameSimilarityThreshold))
+ self.c.SaveAppStateAndLogError()
+
+ currentContext := self.c.CurrentStaticContext()
+ switch currentContext.GetKey() {
+ // we make an exception for our files context, because it actually need to refresh its state afterwards.
+ case context.FILES_CONTEXT_KEY:
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
+ default:
+ return currentContext.HandleRenderToMain()
+ }
+}
+
+func (self *RenameSimilarityThresholdController) isShowingRenames() bool {
+ return lo.Contains(
+ CONTEXT_KEYS_SHOWING_RENAMES,
+ self.c.CurrentStaticContext().GetKey(),
+ )
+}
diff --git a/pkg/gui/controllers/screen_mode_actions.go b/pkg/gui/controllers/screen_mode_actions.go
index 1db27f2e25d..2d0026793c5 100644
--- a/pkg/gui/controllers/screen_mode_actions.go
+++ b/pkg/gui/controllers/screen_mode_actions.go
@@ -1,6 +1,7 @@
package controllers
import (
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -16,7 +17,7 @@ func (self *ScreenModeActions) Next() error {
),
)
- return nil
+ return self.rerenderViewsWithScreenModeDependentContent()
}
func (self *ScreenModeActions) Prev() error {
@@ -27,9 +28,33 @@ func (self *ScreenModeActions) Prev() error {
),
)
+ return self.rerenderViewsWithScreenModeDependentContent()
+}
+
+// these views need to be re-rendered when the screen mode changes. The commits view,
+// for example, will show authorship information in half and full screen mode.
+func (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() error {
+ for _, context := range self.c.Context().AllList() {
+ if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES {
+ if err := self.rerenderView(context.GetView()); err != nil {
+ return err
+ }
+ }
+ }
+
return nil
}
+func (self *ScreenModeActions) rerenderView(view *gocui.View) error {
+ context, ok := self.c.Helpers().View.ContextForView(view.Name())
+ if !ok {
+ self.c.Log.Errorf("no context found for view %s", view.Name())
+ return nil
+ }
+
+ return context.HandleRender()
+}
+
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
for i, val := range sl {
if val == current {
diff --git a/pkg/gui/controllers/status_controller.go b/pkg/gui/controllers/status_controller.go
index 4c4384bfd9a..483acdda6a9 100644
--- a/pkg/gui/controllers/status_controller.go
+++ b/pkg/gui/controllers/status_controller.go
@@ -12,6 +12,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -116,7 +117,7 @@ func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
return err
}
- upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig)
+ upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig))
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {
diff --git a/pkg/gui/controllers/suggestions_controller.go b/pkg/gui/controllers/suggestions_controller.go
index 857952d9bf5..a425f356ef5 100644
--- a/pkg/gui/controllers/suggestions_controller.go
+++ b/pkg/gui/controllers/suggestions_controller.go
@@ -40,11 +40,8 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []
Handler: func() error { return self.context().State.OnClose() },
},
{
- Key: opts.GetKey(opts.Config.Universal.TogglePanel),
- Handler: func() error {
- self.c.Views().Suggestions.Subtitle = ""
- return self.c.ReplaceContext(self.c.Contexts().Confirmation)
- },
+ Key: opts.GetKey(opts.Config.Universal.TogglePanel),
+ Handler: self.switchToConfirmation,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
@@ -61,7 +58,7 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []
self.c.Contexts().Confirmation.GetView().TextArea.TypeString(selectedItem.Value)
self.c.Contexts().Confirmation.GetView().RenderTextArea()
self.c.Contexts().Suggestions.RefreshSuggestions()
- return self.c.ReplaceContext(self.c.Contexts().Confirmation)
+ return self.switchToConfirmation()
}
}
return nil
@@ -72,6 +69,12 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []
return bindings
}
+func (self *SuggestionsController) switchToConfirmation() error {
+ self.c.Views().Suggestions.Subtitle = ""
+ self.c.Views().Suggestions.Highlight = false
+ return self.c.ReplaceContext(self.c.Contexts().Confirmation)
+}
+
func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.c.Helpers().Confirmation.DeactivateConfirmationPrompt()
diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go
index 7d7ca9eed7d..8c733403127 100644
--- a/pkg/gui/controllers/sync_controller.go
+++ b/pkg/gui/controllers/sync_controller.go
@@ -89,7 +89,7 @@ func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func(
func (self *SyncController) push(currentBranch *models.Branch) error {
// if we are behind our upstream branch we'll ask if the user wants to force push
if currentBranch.IsTrackingRemote() {
- opts := pushOpts{}
+ opts := pushOpts{remoteBranchStoredLocally: currentBranch.RemoteBranchStoredLocally()}
if currentBranch.IsBehindForPush() {
return self.requestToForcePush(currentBranch, opts)
} else {
@@ -180,9 +180,16 @@ func (self *SyncController) pullWithLock(task gocui.Task, opts PullFilesOptions)
type pushOpts struct {
force bool
+ forceWithLease bool
upstreamRemote string
upstreamBranch string
setUpstream bool
+
+ // If this is false, we can't tell ahead of time whether a force-push will
+ // be necessary, so we start with a normal push and offer to force-push if
+ // the server rejected. If this is true, we don't offer to force-push if the
+ // server rejected, but rather ask the user to fetch.
+ remoteBranchStoredLocally bool
}
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
@@ -192,13 +199,32 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
task,
git_commands.PushOpts{
Force: opts.force,
+ ForceWithLease: opts.forceWithLease,
UpstreamRemote: opts.upstreamRemote,
UpstreamBranch: opts.upstreamBranch,
SetUpstream: opts.setUpstream,
})
if err != nil {
- if strings.Contains(err.Error(), "Updates were rejected") {
- return errors.New(self.c.Tr.UpdatesRejected)
+ if !opts.force && !opts.forceWithLease && strings.Contains(err.Error(), "Updates were rejected") {
+ if opts.remoteBranchStoredLocally {
+ return errors.New(self.c.Tr.UpdatesRejected)
+ }
+
+ forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
+ if forcePushDisabled {
+ return errors.New(self.c.Tr.UpdatesRejectedAndForcePushDisabled)
+ }
+ _ = self.c.Confirm(types.ConfirmOpts{
+ Title: self.c.Tr.ForcePush,
+ Prompt: self.forcePushPrompt(),
+ HandleConfirm: func() error {
+ newOpts := opts
+ newOpts.force = true
+
+ return self.pushAux(currentBranch, newOpts)
+ },
+ })
+ return nil
}
return err
}
@@ -216,7 +242,7 @@ func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opt
Title: self.c.Tr.ForcePush,
Prompt: self.forcePushPrompt(),
HandleConfirm: func() error {
- opts.force = true
+ opts.forceWithLease = true
return self.pushAux(currentBranch, opts)
},
})
diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go
index d845f192d49..623ef72179f 100644
--- a/pkg/gui/controllers/tags_controller.go
+++ b/pkg/gui/controllers/tags_controller.go
@@ -74,6 +74,14 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
DisplayOnScreen: true,
OpensMenu: true,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
+ Handler: self.withItem(func(selectedTag *models.Tag) error {
+ return self.c.Helpers().Diff.OpenDiffToolForRef(selectedTag)
+ }),
+ GetDisabledReason: self.require(self.singleItemSelected()),
+ Description: self.c.Tr.OpenDiffTool,
+ },
}
return bindings
diff --git a/pkg/gui/dummies.go b/pkg/gui/dummies.go
index 7bc36ff339b..2350d215e94 100644
--- a/pkg/gui/dummies.go
+++ b/pkg/gui/dummies.go
@@ -17,6 +17,6 @@ func NewDummyUpdater() *updates.Updater {
// NewDummyGui creates a new dummy GUI for testing
func NewDummyGui() *Gui {
newAppConfig := config.NewDummyAppConfig()
- dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{}, NewDummyUpdater(), false, "", nil)
+ dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{Major: 2, Minor: 0, Patch: 0}, NewDummyUpdater(), false, "", nil)
return dummyGui
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index caa385c3b58..66fe5cb9ced 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -342,15 +342,8 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
return nil
}
-// resetState determines if we pull the repo state from our repo state map or
-// just re-initialize it. For now we're only re-using state when we're going
-// in and out of submodules, for the sake of having the cursor back on the submodule
-// when we return.
-//
-// I tried out always reverting to the repo's original state but found that in fact
-// 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
+// resetState reuses the repo state from our repo state map, if the repo was
+// open before; otherwise it creates a new one.
func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
worktreePath := gui.git.RepoPaths.WorktreePath()
@@ -386,6 +379,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{},
+ MainBranches: git_commands.NewMainBranches(gui.UserConfig.Git.MainBranches, gui.os.Cmd),
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath, ""),
@@ -684,7 +678,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
return err
}
- gui.g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
+ gui.g.SetManager(gocui.ManagerFunc(gui.layout))
if err := gui.createAllViews(); err != nil {
return err
@@ -965,3 +959,12 @@ func (gui *Gui) onWorker(f func(gocui.Task) error) {
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus)
}
+
+func (gui *Gui) afterLayout(f func() error) {
+ select {
+ case gui.afterLayoutFuncs <- f:
+ default:
+ // hopefully this never happens
+ gui.c.Log.Error("afterLayoutFuncs channel is full, skipping function")
+ }
+}
diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go
index a75aa3658fc..8a537f2ca2b 100644
--- a/pkg/gui/gui_common.go
+++ b/pkg/gui/gui_common.go
@@ -73,6 +73,10 @@ func (self *guiCommon) CurrentSideContext() types.Context {
return self.gui.State.ContextMgr.CurrentSide()
}
+func (self *guiCommon) CurrentPopupContexts() []types.Context {
+ return self.gui.State.ContextMgr.PopupContexts()
+}
+
func (self *guiCommon) IsCurrentContext(c types.Context) bool {
return self.gui.State.ContextMgr.IsCurrent(c)
}
@@ -189,12 +193,7 @@ func (self *guiCommon) GetInitialKeybindingsWithCustomCommands() ([]*types.Bindi
}
func (self *guiCommon) AfterLayout(f func() error) {
- select {
- case self.gui.afterLayoutFuncs <- f:
- default:
- // hopefully this never happens
- self.gui.c.Log.Error("afterLayoutFuncs channel is full, skipping function")
- }
+ self.gui.afterLayout(f)
}
func (self *guiCommon) RunningIntegrationTest() bool {
diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go
index 3421602e9e2..5de0ad4eed6 100644
--- a/pkg/gui/gui_driver.go
+++ b/pkg/gui/gui_driver.go
@@ -21,6 +21,7 @@ type GuiDriver struct {
gui *Gui
isIdleChan chan struct{}
toastChan chan string
+ headless bool
}
var _ integrationTypes.GuiDriver = &GuiDriver{}
@@ -161,3 +162,7 @@ func (self *GuiDriver) NextToast() *string {
return nil
}
}
+
+func (self *GuiDriver) Headless() bool {
+ return self.headless
+}
diff --git a/pkg/gui/information_panel.go b/pkg/gui/information_panel.go
index 00867fb9254..3eac1e77cf4 100644
--- a/pkg/gui/information_panel.go
+++ b/pkg/gui/information_panel.go
@@ -6,7 +6,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/utils"
- "github.com/mattn/go-runewidth"
)
func (gui *Gui) informationStr() string {
@@ -34,7 +33,7 @@ func (gui *Gui) handleInfoClick() error {
width, _ := view.Size()
if activeMode, ok := gui.helpers.Mode.GetActiveMode(); ok {
- if width-cx > runewidth.StringWidth(gui.c.Tr.ResetInParentheses) {
+ if width-cx > utils.StringWidth(gui.c.Tr.ResetInParentheses) {
return nil
}
return activeMode.Reset()
@@ -43,10 +42,10 @@ func (gui *Gui) handleInfoClick() error {
var title, url string
// if we're not in an active mode we show the donate button
- if cx <= runewidth.StringWidth(gui.c.Tr.Donate) {
+ if cx <= utils.StringWidth(gui.c.Tr.Donate) {
url = constants.Links.Donate
title = gui.c.Tr.Donate
- } else if cx <= runewidth.StringWidth(gui.c.Tr.Donate)+1+runewidth.StringWidth(gui.c.Tr.AskQuestion) {
+ } else if cx <= utils.StringWidth(gui.c.Tr.Donate)+1+utils.StringWidth(gui.c.Tr.AskQuestion) {
url = constants.Links.Discussions
title = gui.c.Tr.AskQuestion
}
diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go
index 01c39762879..3bf6a7c35c8 100644
--- a/pkg/gui/layout.go
+++ b/pkg/gui/layout.go
@@ -72,14 +72,26 @@ func (gui *Gui) layout(g *gocui.Gui) error {
frameOffset = 0
}
- if context.NeedsRerenderOnWidthChange() {
+ mustRerender := false
+ if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES {
// view.Width() returns the width -1 for some reason
oldWidth := view.Width() + 1
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
if oldWidth != newWidth {
- contextsToRerender = append(contextsToRerender, context)
+ mustRerender = true
}
}
+ if context.NeedsRerenderOnHeightChange() {
+ // view.Height() returns the height -1 for some reason
+ oldHeight := view.Height() + 1
+ newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 2*frameOffset
+ if oldHeight != newHeight {
+ mustRerender = true
+ }
+ }
+ if mustRerender {
+ contextsToRerender = append(contextsToRerender, context)
+ }
_, err = g.SetView(
viewName,
@@ -161,9 +173,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
// if you run `lazygit --logs`
// this will let you see these branches as prettified json
// gui.c.Log.Info(utils.AsJson(gui.State.Model.Branches[0:4]))
- if err := gui.helpers.Confirmation.ResizeCurrentPopupPanel(); err != nil {
- return err
- }
+ gui.helpers.Confirmation.ResizeCurrentPopupPanels()
gui.renderContextOptionsMap()
@@ -276,35 +286,6 @@ func (gui *Gui) onInitialViewsCreation() error {
return nil
}
-// getFocusLayout returns a manager function for when view gain and lose focus
-func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
- var previousView *gocui.View
- return func(g *gocui.Gui) error {
- newView := gui.g.CurrentView()
- // for now we don't consider losing focus to a popup panel as actually losing focus
- if newView != previousView && !gui.helpers.Confirmation.IsPopupPanel(newView.Name()) {
- if err := gui.onViewFocusLost(previousView); err != nil {
- return err
- }
-
- previousView = newView
- }
- return nil
- }
-}
-
-func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
- if oldView == nil {
- return nil
- }
-
- oldView.Highlight = false
-
- _ = oldView.SetOriginX(0)
-
- return nil
-}
-
func (gui *Gui) transientContexts() []types.Context {
return lo.Filter(gui.State.Contexts.Flatten(), func(context types.Context, _ int) bool {
return context.IsTransient()
diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go
index bf30331cd92..49d278399f1 100644
--- a/pkg/gui/main_panels.go
+++ b/pkg/gui/main_panels.go
@@ -20,7 +20,10 @@ func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error {
return gui.newCmdTask(view, v.Cmd, v.Prefix)
case *types.RunPtyTask:
- return gui.newPtyTask(view, v.Cmd, v.Prefix)
+ gui.afterLayout(func() error {
+ return gui.newPtyTask(view, v.Cmd, v.Prefix)
+ })
+ return nil
}
return nil
diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go
index b777536eee8..ca03ea69e6e 100644
--- a/pkg/gui/menu_panel.go
+++ b/pkg/gui/menu_panel.go
@@ -42,6 +42,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
}
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
+ gui.State.Contexts.Menu.SetPrompt(opts.Prompt)
gui.State.Contexts.Menu.SetSelection(0)
gui.Views.Menu.Title = opts.Title
diff --git a/pkg/gui/presentation/authors/authors.go b/pkg/gui/presentation/authors/authors.go
index 8f63258410e..28c375f7522 100644
--- a/pkg/gui/presentation/authors/authors.go
+++ b/pkg/gui/presentation/authors/authors.go
@@ -11,11 +11,16 @@ import (
"github.com/mattn/go-runewidth"
)
+type authorNameCacheKey struct {
+ authorName string
+ truncateTo int
+}
+
// if these being global variables causes trouble we can wrap them in a struct
// attached to the gui state.
var (
authorInitialCache = make(map[string]string)
- authorNameCache = make(map[string]string)
+ authorNameCache = make(map[authorNameCacheKey]string)
authorStyleCache = make(map[string]style.TextStyle)
)
@@ -37,19 +42,37 @@ func ShortAuthor(authorName string) string {
return value
}
-func LongAuthor(authorName string) string {
- if value, ok := authorNameCache[authorName]; ok {
+func LongAuthor(authorName string, length int) string {
+ cacheKey := authorNameCacheKey{authorName: authorName, truncateTo: length}
+ if value, ok := authorNameCache[cacheKey]; ok {
return value
}
- paddedAuthorName := utils.WithPadding(authorName, 17, utils.AlignLeft)
- truncatedName := utils.TruncateWithEllipsis(paddedAuthorName, 17)
+ paddedAuthorName := utils.WithPadding(authorName, length, utils.AlignLeft)
+ truncatedName := utils.TruncateWithEllipsis(paddedAuthorName, length)
value := AuthorStyle(authorName).Sprint(truncatedName)
- authorNameCache[authorName] = value
+ authorNameCache[cacheKey] = value
return value
}
+// AuthorWithLength returns a representation of the author that fits into a
+// given maximum length:
+// - if the length is less than 2, it returns an empty string
+// - if the length is 2, it returns the initials
+// - otherwise, it returns the author name truncated to the maximum length
+func AuthorWithLength(authorName string, length int) string {
+ if length < 2 {
+ return ""
+ }
+
+ if length == 2 {
+ return ShortAuthor(authorName)
+ }
+
+ return LongAuthor(authorName, length)
+}
+
func AuthorStyle(authorName string) style.TextStyle {
if value, ok := authorStyleCache[authorName]; ok {
return value
diff --git a/pkg/gui/presentation/authors/authors_test.go b/pkg/gui/presentation/authors/authors_test.go
index 58efba29757..d7c651031e1 100644
--- a/pkg/gui/presentation/authors/authors_test.go
+++ b/pkg/gui/presentation/authors/authors_test.go
@@ -1,6 +1,11 @@
package authors
-import "testing"
+import (
+ "testing"
+
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/stretchr/testify/assert"
+)
func TestGetInitials(t *testing.T) {
for input, expectedOutput := range map[string]string{
@@ -18,3 +23,21 @@ func TestGetInitials(t *testing.T) {
}
}
}
+
+func TestAuthorWithLength(t *testing.T) {
+ scenarios := []struct {
+ authorName string
+ length int
+ expectedOutput string
+ }{
+ {"Jesse Duffield", 0, ""},
+ {"Jesse Duffield", 1, ""},
+ {"Jesse Duffield", 2, "JD"},
+ {"Jesse Duffield", 3, "Je…"},
+ {"Jesse Duffield", 10, "Jesse Duf…"},
+ {"Jesse Duffield", 14, "Jesse Duffield"},
+ }
+ for _, s := range scenarios {
+ assert.Equal(t, s.expectedOutput, utils.Decolorise(AuthorWithLength(s.authorName, s.length)))
+ }
+}
diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go
index 406a580d5fd..b75dfc95b72 100644
--- a/pkg/gui/presentation/branches.go
+++ b/pkg/gui/presentation/branches.go
@@ -56,7 +56,7 @@ func getBranchDisplayStrings(
// Recency is always three characters, plus one for the space
availableWidth := viewWidth - 4
if len(branchStatus) > 0 {
- availableWidth -= runewidth.StringWidth(branchStatus) + 1
+ availableWidth -= utils.StringWidth(utils.Decolorise(branchStatus)) + 1
}
if icons.IsIconEnabled() {
availableWidth -= 2 // one for the icon, one for the space
@@ -65,7 +65,7 @@ func getBranchDisplayStrings(
availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1
}
if checkedOutByWorkTree {
- availableWidth -= runewidth.StringWidth(worktreeIcon) + 1
+ availableWidth -= utils.StringWidth(worktreeIcon) + 1
}
displayName := b.Name
@@ -79,18 +79,17 @@ func getBranchDisplayStrings(
}
// Don't bother shortening branch names that are already 3 characters or less
- if len(displayName) > max(availableWidth, 3) {
+ if utils.StringWidth(displayName) > max(availableWidth, 3) {
// Never shorten the branch name to less then 3 characters
len := max(availableWidth, 4)
- displayName = displayName[:len-1] + "…"
+ displayName = runewidth.Truncate(displayName, len, "…")
}
coloredName := nameTextStyle.Sprint(displayName)
if checkedOutByWorkTree {
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
}
if len(branchStatus) > 0 {
- coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus)
- coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus)
+ coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
}
recencyColor := style.FgCyan
@@ -144,30 +143,6 @@ func GetBranchTextStyle(name string) style.TextStyle {
}
}
-func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle {
- colour := style.FgYellow
- if itemOperation != types.ItemOperationNone {
- colour = style.FgCyan
- } else if branch.UpstreamGone {
- colour = style.FgRed
- } else if branch.MatchesUpstream() {
- colour = style.FgGreen
- } else if branch.RemoteBranchNotStoredLocally() {
- colour = style.FgMagenta
- }
-
- return colour
-}
-
-func ColoredBranchStatus(
- branch *models.Branch,
- itemOperation types.ItemOperation,
- tr *i18n.TranslationSet,
- userConfig *config.UserConfig,
-) string {
- return branchStatusColor(branch, itemOperation).Sprint(BranchStatus(branch, itemOperation, tr, time.Now(), userConfig))
-}
-
func BranchStatus(
branch *models.Branch,
itemOperation types.ItemOperation,
@@ -177,30 +152,38 @@ func BranchStatus(
) string {
itemOperationStr := ItemOperationToString(itemOperation, tr)
if itemOperationStr != "" {
- return itemOperationStr + " " + utils.Loader(now, userConfig.Gui.Spinner)
- }
-
- if !branch.IsTrackingRemote() {
- return ""
- }
-
- if branch.UpstreamGone {
- return tr.UpstreamGone
- }
-
- if branch.MatchesUpstream() {
- return "✓"
- }
- if branch.RemoteBranchNotStoredLocally() {
- return "?"
+ return style.FgCyan.Sprintf("%s %s", itemOperationStr, utils.Loader(now, userConfig.Gui.Spinner))
}
result := ""
- if branch.IsAheadForPull() {
- result = fmt.Sprintf("↑%s", branch.AheadForPull)
- }
- if branch.IsBehindForPull() {
- result = fmt.Sprintf("%s↓%s", result, branch.BehindForPull)
+ if branch.IsTrackingRemote() {
+ if branch.UpstreamGone {
+ result = style.FgRed.Sprint(tr.UpstreamGone)
+ } else if branch.MatchesUpstream() {
+ result = style.FgGreen.Sprint("✓")
+ } else if branch.RemoteBranchNotStoredLocally() {
+ result = style.FgMagenta.Sprint("?")
+ } else if branch.IsBehindForPull() && branch.IsAheadForPull() {
+ result = style.FgYellow.Sprintf("↓%s↑%s", branch.BehindForPull, branch.AheadForPull)
+ } else if branch.IsBehindForPull() {
+ result = style.FgYellow.Sprintf("↓%s", branch.BehindForPull)
+ } else if branch.IsAheadForPull() {
+ result = style.FgYellow.Sprintf("↑%s", branch.AheadForPull)
+ }
+ }
+
+ if userConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
+ behind := branch.BehindBaseBranch.Load()
+ if behind != 0 {
+ if result != "" {
+ result += " "
+ }
+ if userConfig.Gui.ShowDivergenceFromBaseBranch == "arrowAndNumber" {
+ result += style.FgCyan.Sprintf("↓%d", behind)
+ } else {
+ result += style.FgCyan.Sprintf("↓")
+ }
+ }
}
return result
diff --git a/pkg/gui/presentation/branches_test.go b/pkg/gui/presentation/branches_test.go
index cf2f1d994f8..71dc89c8ab9 100644
--- a/pkg/gui/presentation/branches_test.go
+++ b/pkg/gui/presentation/branches_test.go
@@ -2,6 +2,7 @@ package presentation
import (
"fmt"
+ "sync/atomic"
"testing"
"time"
@@ -15,6 +16,11 @@ import (
"github.com/xo/terminfo"
)
+func makeAtomic(v int32) (result atomic.Int32) {
+ result.Store(v)
+ return //nolint: nakedret
+}
+
func Test_getBranchDisplayStrings(t *testing.T) {
scenarios := []struct {
branch *models.Branch
@@ -23,6 +29,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth int
useIcons bool
checkedOutByWorktree bool
+ showDivergenceCfg string
expected []string
}{
// First some tests for when the view is wide enough so that everything fits:
@@ -33,8 +40,19 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branch_name"},
},
+ {
+ branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"},
+ itemOperation: types.ItemOperationNone,
+ fullDescription: false,
+ viewWidth: 19,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
+ expected: []string{"1m", "🍉_special_char"},
+ },
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
@@ -42,6 +60,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: true,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branch_name (worktree)"},
},
{
@@ -51,6 +70,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: true,
checkedOutByWorktree: true,
+ showDivergenceCfg: "none",
expected: []string{"1m", "", "branch_name "},
},
{
@@ -66,6 +86,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branch_name ✓"},
},
{
@@ -81,7 +102,56 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: true,
- expected: []string{"1m", "branch_name (worktree) ↑3↓5"},
+ showDivergenceCfg: "none",
+ expected: []string{"1m", "branch_name (worktree) ↓5↑3"},
+ },
+ {
+ branch: &models.Branch{
+ Name: "branch_name",
+ Recency: "1m",
+ BehindBaseBranch: makeAtomic(2),
+ },
+ itemOperation: types.ItemOperationNone,
+ fullDescription: false,
+ viewWidth: 100,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ showDivergenceCfg: "onlyArrow",
+ expected: []string{"1m", "branch_name ↓"},
+ },
+ {
+ branch: &models.Branch{
+ Name: "branch_name",
+ Recency: "1m",
+ UpstreamRemote: "origin",
+ AheadForPull: "0",
+ BehindForPull: "0",
+ BehindBaseBranch: makeAtomic(2),
+ },
+ itemOperation: types.ItemOperationNone,
+ fullDescription: false,
+ viewWidth: 100,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ showDivergenceCfg: "arrowAndNumber",
+ expected: []string{"1m", "branch_name ✓ ↓2"},
+ },
+ {
+ branch: &models.Branch{
+ Name: "branch_name",
+ Recency: "1m",
+ UpstreamRemote: "origin",
+ AheadForPull: "3",
+ BehindForPull: "5",
+ BehindBaseBranch: makeAtomic(2),
+ },
+ itemOperation: types.ItemOperationNone,
+ fullDescription: false,
+ viewWidth: 100,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ showDivergenceCfg: "arrowAndNumber",
+ expected: []string{"1m", "branch_name ↓5↑3 ↓2"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -90,6 +160,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branch_name Pushing |"},
},
{
@@ -108,6 +179,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
},
@@ -119,8 +191,19 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 14,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branch_na…"},
},
+ {
+ branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"},
+ itemOperation: types.ItemOperationNone,
+ fullDescription: false,
+ viewWidth: 18,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
+ expected: []string{"1m", "🍉_special_ch…"},
+ },
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
@@ -128,6 +211,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 14,
useIcons: false,
checkedOutByWorktree: true,
+ showDivergenceCfg: "none",
expected: []string{"1m", "bra… (worktree)"},
},
{
@@ -137,6 +221,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 14,
useIcons: true,
checkedOutByWorktree: true,
+ showDivergenceCfg: "none",
expected: []string{"1m", "", "branc… "},
},
{
@@ -152,6 +237,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 14,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branch_… ✓"},
},
{
@@ -167,7 +253,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 30,
useIcons: false,
checkedOutByWorktree: true,
- expected: []string{"1m", "branch_na… (worktree) ↑3↓5"},
+ showDivergenceCfg: "none",
+ expected: []string{"1m", "branch_na… (worktree) ↓5↑3"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -176,6 +263,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 20,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "branc… Pushing |"},
},
{
@@ -185,6 +273,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: -1,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "abc Pushing |"},
},
{
@@ -194,6 +283,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: -1,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "ab Pushing |"},
},
{
@@ -203,6 +293,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: -1,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "a Pushing |"},
},
{
@@ -221,6 +312,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 20,
useIcons: false,
checkedOutByWorktree: false,
+ showDivergenceCfg: "none",
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
},
}
@@ -232,6 +324,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
for i, s := range scenarios {
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
+ c.UserConfig.Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg
worktrees := []*models.Worktree{}
if s.checkedOutByWorktree {
diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go
index c385d3407b2..92327462f71 100644
--- a/pkg/gui/presentation/commits.go
+++ b/pkg/gui/presentation/commits.go
@@ -440,10 +440,11 @@ func displayCommit(
mark = fmt.Sprintf("%s ", willBeRebased)
}
- authorFunc := authors.ShortAuthor
+ authorLength := common.UserConfig.Gui.CommitAuthorShortLength
if fullDescription {
- authorFunc = authors.LongAuthor
+ authorLength = common.UserConfig.Gui.CommitAuthorLongLength
}
+ author := authors.AuthorWithLength(commit.AuthorName, authorLength)
cols := make([]string, 0, 7)
cols = append(
@@ -453,7 +454,7 @@ func displayCommit(
bisectString,
descriptionString,
actionString,
- authorFunc(commit.AuthorName),
+ author,
graphLine+mark+tagString+theme.DefaultTextColor.Sprint(name),
)
diff --git a/pkg/gui/presentation/icons/file_icons.go b/pkg/gui/presentation/icons/file_icons.go
index 05832c55db8..8f639a4eead 100644
--- a/pkg/gui/presentation/icons/file_icons.go
+++ b/pkg/gui/presentation/icons/file_icons.go
@@ -4,239 +4,557 @@ import (
"path/filepath"
)
-// https://github.com/ogham/exa/blob/master/src/output/icons.rs
+// NOTE: Visit next links for inspiration:
+// https://github.com/eza-community/eza/blob/main/src/output/icons.rs
+// https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua
+
var (
DEFAULT_FILE_ICON = IconProperties{Icon: "\uf15b", Color: 241} //
DEFAULT_SUBMODULE_ICON = IconProperties{Icon: "\uf1d3", Color: 202} //
DEFAULT_DIRECTORY_ICON = IconProperties{Icon: "\uf07b", Color: 241} //
)
-// See https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua
var nameIconMap = map[string]IconProperties{
- ".Trash": {Icon: "\uf1f8", Color: 241}, //
- ".atom": {Icon: "\ue764", Color: 241}, //
- ".bashprofile": {Icon: "\ue615", Color: 113}, //
- ".bashrc": {Icon: "\ue795", Color: 113}, //
- ".idea": {Icon: "\ue7b5", Color: 241}, //
- ".git": {Icon: "\uf1d3", Color: 202}, //
- ".gitattributes": {Icon: "\uf1d3", Color: 202}, //
- ".gitconfig": {Icon: "\uf1d3", Color: 202}, //
- ".github": {Icon: "\uf408", Color: 241}, //
- ".gitignore": {Icon: "\uf1d3", Color: 202}, //
- ".gitmodules": {Icon: "\uf1d3", Color: 202}, //
- ".rvm": {Icon: "\ue21e", Color: 160}, //
- ".vimrc": {Icon: "\ue62b", Color: 28}, //
- ".vscode": {Icon: "\ue70c", Color: 39}, //
- ".zshrc": {Icon: "\ue795", Color: 113}, //
- "Cargo.lock": {Icon: "\ue7a8", Color: 216}, //
- "Cargo.toml": {Icon: "\ue7a8", Color: 216}, //
- "bin": {Icon: "\ue5fc", Color: 241}, //
- "config": {Icon: "\ue5fc", Color: 241}, //
- "docker-compose.yml": {Icon: "\uf308", Color: 68}, //
- "Dockerfile": {Icon: "\uf308", Color: 68}, //
- "ds_store": {Icon: "\uf179", Color: 15}, //
- "gitignore_global": {Icon: "\uf1d3", Color: 202}, //
- "go.mod": {Icon: "\ue627", Color: 74}, //
- "go.sum": {Icon: "\ue627", Color: 74}, //
- "gradle": {Icon: "\ue256", Color: 168}, //
- "gruntfile.coffee": {Icon: "\ue611", Color: 166}, //
- "gruntfile.js": {Icon: "\ue611", Color: 166}, //
- "gruntfile.ls": {Icon: "\ue611", Color: 166}, //
- "gulpfile.coffee": {Icon: "\ue610", Color: 167}, //
- "gulpfile.js": {Icon: "\ue610", Color: 167}, //
- "gulpfile.ls": {Icon: "\ue610", Color: 168}, //
- "hidden": {Icon: "\uf023", Color: 241}, //
- "include": {Icon: "\ue5fc", Color: 241}, //
- "lib": {Icon: "\uf121", Color: 241}, //
- "localized": {Icon: "\uf179", Color: 15}, //
- "Makefile": {Icon: "\ue975", Color: 241}, //
- "node_modules": {Icon: "\ue718", Color: 197}, //
- "npmignore": {Icon: "\ue71e", Color: 197}, //
- "PKGBUILD": {Icon: "\uf303", Color: 38}, //
- "rubydoc": {Icon: "\ue73b", Color: 160}, //
- "yarn.lock": {Icon: "\ue6a7", Color: 74}, //
+ ".atom": {Icon: "\ue764", Color: 241}, //
+ ".babelrc": {Icon: "\ue639", Color: 185}, //
+ ".bash_profile": {Icon: "\ue615", Color: 113}, //
+ ".bashprofile": {Icon: "\ue615", Color: 113}, //
+ ".bashrc": {Icon: "\ue795", Color: 113}, //
+ ".dockerignore": {Icon: "\uf0868", Color: 68}, //
+ ".ds_store": {Icon: "\ue615", Color: 239}, //
+ ".editorconfig": {Icon: "\ue652", Color: 255}, //
+ ".env": {Icon: "\uf462", Color: 227}, //
+ ".eslintignore": {Icon: "\ue655", Color: 56}, //
+ ".eslintrc": {Icon: "\ue655", Color: 56}, //
+ ".gitattributes": {Icon: "\U000f02a2", Color: 202}, //
+ ".git-blame-ignore-revs": {Icon: "\ue702", Color: 196}, //
+ ".gitconfig": {Icon: "\U000f02a2", Color: 202}, //
+ ".github": {Icon: "\uf408", Color: 241}, //
+ ".git": {Icon: "\U000f02a2", Color: 202}, //
+ ".gitignore": {Icon: "\U000f02a2", Color: 202}, //
+ ".gitlab-ci.yml": {Icon: "\uf296", Color: 196}, //
+ ".gitmodules": {Icon: "\U000f02a2", Color: 202}, //
+ ".gtkrc-2.0": {Icon: "\uf362", Color: 231}, //
+ ".gvimrc": {Icon: "\ue62b", Color: 28}, //
+ "_gvimrc": {Icon: "\ue62b", Color: 28}, //
+ ".idea": {Icon: "\ue7b5", Color: 241}, //
+ ".justfile": {Icon: "\uf0ad", Color: 66}, //
+ ".luaurc": {Icon: "\ue615", Color: 75}, //
+ ".mailmap": {Icon: "\U000f02a2", Color: 202}, //
+ ".npmignore": {Icon: "\ue71e", Color: 197}, //
+ ".npmrc": {Icon: "\ue71e", Color: 197}, //
+ ".nuxtrc": {Icon: "\uf1106", Color: 42}, //
+ ".nvmrc": {Icon: "\ue718", Color: 71}, //
+ ".prettierignore": {Icon: "\ue6b4", Color: 33}, //
+ ".prettierrc": {Icon: "\ue6b4", Color: 33}, //
+ ".prettierrc.json5": {Icon: "\ue6b4", Color: 33}, //
+ ".prettierrc.json": {Icon: "\ue6b4", Color: 33}, //
+ ".prettierrc.toml": {Icon: "\ue6b4", Color: 33}, //
+ ".prettierrc.yaml": {Icon: "\ue6b4", Color: 33}, //
+ ".prettierrc.yml": {Icon: "\ue6b4", Color: 33}, //
+ ".rvm": {Icon: "\ue21e", Color: 160}, //
+ ".settings.json": {Icon: "\ue70c", Color: 98}, //
+ ".SRCINFO": {Icon: "\uf129", Color: 230}, //
+ ".Trash": {Icon: "\uf1f8", Color: 241}, //
+ ".vimrc": {Icon: "\ue62b", Color: 28}, //
+ "_vimrc": {Icon: "\ue62b", Color: 28}, //
+ ".vscode": {Icon: "\ue70c", Color: 39}, //
+ ".Xauthority": {Icon: "\uf369", Color: 196}, //
+ ".xinitrc": {Icon: "\uf369", Color: 196}, //
+ ".Xresources": {Icon: "\uf369", Color: 196}, //
+ ".xsession": {Icon: "\uf369", Color: 196}, //
+ ".zprofile": {Icon: "\ue615", Color: 113}, //
+ ".zshenv": {Icon: "\ue615", Color: 113}, //
+ ".zshrc": {Icon: "\ue795", Color: 113}, //
+ "bin": {Icon: "\ue5fc", Color: 241}, //
+ "brewfile": {Icon: "\ue791", Color: 52}, //
+ "bspwmrc": {Icon: "\uf355", Color: 236}, //
+ "build.gradle": {Icon: "\ue660", Color: 24}, //
+ "build": {Icon: "\ue63a", Color: 113}, //
+ "build.zig.zon": {Icon: "\ue6a9", Color: 172}, //
+ "cantorrc": {Icon: "\uf373", Color: 32}, //
+ "Cargo.lock": {Icon: "\ue7a8", Color: 216}, //
+ "Cargo.toml": {Icon: "\ue7a8", Color: 216}, //
+ "checkhealth": {Icon: "\uf04d9", Color: 75}, //
+ "cmakelists.txt": {Icon: "\ue615", Color: 66}, //
+ "commit_editmsg": {Icon: "\ue702", Color: 196}, //
+ "COMMIT_EDITMSG": {Icon: "\ue702", Color: 239}, //
+ "commitlint.config.js": {Icon: "\uf0718", Color: 30}, //
+ "commitlint.config.ts": {Icon: "\uf0718", Color: 30}, //
+ "compose.yaml": {Icon: "\uf308", Color: 68}, //
+ "compose.yml": {Icon: "\uf308", Color: 68}, //
+ "config": {Icon: "\ue5fc", Color: 241}, //
+ "containerfile": {Icon: "\uf0868", Color: 68}, //
+ "copying": {Icon: "\ue60a", Color: 185}, //
+ "copying.lesser": {Icon: "\ue60a", Color: 185}, //
+ "docker-compose.yaml": {Icon: "\uf308", Color: 68}, //
+ "docker-compose.yml": {Icon: "\uf308", Color: 68}, //
+ "dockerfile": {Icon: "\uf0868", Color: 68}, //
+ "Dockerfile": {Icon: "\uf308", Color: 68}, //
+ "ds_store": {Icon: "\uf179", Color: 15}, //
+ "eslint.config.cjs": {Icon: "\ue655", Color: 56}, //
+ "eslint.config.js": {Icon: "\ue655", Color: 56}, //
+ "eslint.config.mjs": {Icon: "\ue655", Color: 56}, //
+ "eslint.config.ts": {Icon: "\ue655", Color: 56}, //
+ "ext_typoscript_setup.txt": {Icon: "\ue772", Color: 208}, //
+ "favicon.ico": {Icon: "\ue623", Color: 185}, //
+ "fp-info-cache": {Icon: "\uf49b", Color: 231}, //
+ "fp-lib-table": {Icon: "\uf34c", Color: 231}, //
+ "FreeCAD.conf": {Icon: "\uf336", Color: 160}, //
+ "gemfile$": {Icon: "\ue791", Color: 52}, //
+ "gitignore_global": {Icon: "\U000f02a2", Color: 202}, //
+ "gnumakefile": {Icon: "\ue779", Color: 66}, //
+ "GNUmakefile": {Icon: "\ue779", Color: 66}, //
+ "go.mod": {Icon: "\ue627", Color: 74}, //
+ "go.sum": {Icon: "\ue627", Color: 74}, //
+ "go.work": {Icon: "\ue627", Color: 74}, //
+ "gradle": {Icon: "\ue256", Color: 168}, //
+ "gradle.properties": {Icon: "\ue660", Color: 24}, //
+ "gradlew": {Icon: "\ue660", Color: 24}, //
+ "gradle-wrapper.properties": {Icon: "\ue660", Color: 24}, //
+ "gruntfile.babel.js": {Icon: "\ue611", Color: 166}, //
+ "gruntfile.coffee": {Icon: "\ue611", Color: 166}, //
+ "gruntfile.js": {Icon: "\ue611", Color: 166}, //
+ "gruntfile.ls": {Icon: "\ue611", Color: 166}, //
+ "gruntfile.ts": {Icon: "\ue611", Color: 166}, //
+ "gtkrc": {Icon: "\uf362", Color: 231}, //
+ "gulpfile.babel.js": {Icon: "\ue610", Color: 167}, //
+ "gulpfile.coffee": {Icon: "\ue610", Color: 167}, //
+ "gulpfile.js": {Icon: "\ue610", Color: 167}, //
+ "gulpfile.ls": {Icon: "\ue610", Color: 168}, //
+ "gulpfile.ts": {Icon: "\ue610", Color: 167}, //
+ "hidden": {Icon: "\uf023", Color: 241}, //
+ "hypridle.conf": {Icon: "\uf359", Color: 37}, //
+ "hyprland.conf": {Icon: "\uf359", Color: 37}, //
+ "hyprlock.conf": {Icon: "\uf359", Color: 37}, //
+ "i3blocks.conf": {Icon: "\uf35a", Color: 255}, //
+ "i3status.conf": {Icon: "\uf35a", Color: 255}, //
+ "include": {Icon: "\ue5fc", Color: 241}, //
+ "ionic.config.json": {Icon: "\ue7a9", Color: 33}, //
+ "justfile": {Icon: "\uf0ad", Color: 66}, //
+ "kalgebrarc": {Icon: "\uf373", Color: 32}, //
+ "kdeglobals": {Icon: "\uf373", Color: 32}, //
+ "kdenlive-layoutsrc": {Icon: "\uf33c", Color: 110}, //
+ "kdenliverc": {Icon: "\uf33c", Color: 110}, //
+ "kritadisplayrc": {Icon: "\uf33d", Color: 201}, //
+ "kritarc": {Icon: "\uf33d", Color: 201}, //
+ "lib": {Icon: "\uf121", Color: 241}, //
+ "localized": {Icon: "\uf179", Color: 15}, //
+ "lxde-rc.xml": {Icon: "\uf363", Color: 246}, //
+ "lxqt.conf": {Icon: "\uf364", Color: 32}, //
+ "Makefile": {Icon: "\ue975", Color: 241}, //
+ "mix.lock": {Icon: "\ue62d", Color: 140}, //
+ "mpv.conf": {Icon: "\uf36e", Color: 53}, //
+ "node_modules": {Icon: "\ue718", Color: 197}, //
+ "npmignore": {Icon: "\ue71e", Color: 197}, //
+ "nuxt.config.cjs": {Icon: "\uf1106", Color: 42}, //
+ "nuxt.config.js": {Icon: "\uf1106", Color: 42}, //
+ "nuxt.config.mjs": {Icon: "\uf1106", Color: 42}, //
+ "nuxt.config.ts": {Icon: "\uf1106", Color: 42}, //
+ "package.json": {Icon: "\ue71e", Color: 197}, //
+ "package-lock.json": {Icon: "\ue71e", Color: 52}, //
+ "PKGBUILD": {Icon: "\uf303", Color: 38}, //
+ "platformio.ini": {Icon: "\ue682", Color: 208}, //
+ "pom.xml": {Icon: "\ue674", Color: 52}, //
+ "prettier.config.cjs": {Icon: "\ue6b4", Color: 33}, //
+ "prettier.config.js": {Icon: "\ue6b4", Color: 33}, //
+ "prettier.config.mjs": {Icon: "\ue6b4", Color: 33}, //
+ "prettier.config.ts": {Icon: "\ue6b4", Color: 33}, //
+ "PrusaSlicerGcodeViewer.ini": {Icon: "\uf351", Color: 202}, //
+ "PrusaSlicer.ini": {Icon: "\uf351", Color: 202}, //
+ "py.typed": {Icon: "\ue606", Color: 214}, //
+ "QtProject.conf": {Icon: "\uf375", Color: 77}, //
+ "R": {Icon: "\uf07d4", Color: 25}, //
+ "robots.txt": {Icon: "\uf06a9", Color: 60}, //
+ "rubydoc": {Icon: "\ue73b", Color: 160}, //
+ "settings.gradle": {Icon: "\ue660", Color: 24}, //
+ "svelte.config.js": {Icon: "\ue697", Color: 196}, //
+ "sxhkdrc": {Icon: "\uf355", Color: 236}, //
+ "sym-lib-table": {Icon: "\uf34c", Color: 231}, //
+ "tailwind.config.js": {Icon: "\uf13ff", Color: 45}, //
+ "tailwind.config.mjs": {Icon: "\uf13ff", Color: 45}, //
+ "tailwind.config.ts": {Icon: "\uf13ff", Color: 45}, //
+ "tmux.conf": {Icon: "\uebc8", Color: 34}, //
+ "tmux.conf.local": {Icon: "\uebc8", Color: 34}, //
+ "tsconfig.json": {Icon: "\ue69d", Color: 74}, //
+ "unlicense": {Icon: "\ue60a", Color: 185}, //
+ "vagrantfile$": {Icon: "\uf2b8", Color: 27}, //
+ "vlcrc": {Icon: "\uf057c", Color: 208}, //
+ "webpack": {Icon: "\uf072b", Color: 74}, //
+ "weston.ini": {Icon: "\uf367", Color: 214}, //
+ "workspace": {Icon: "\ue63a", Color: 113}, //
+ "xmobarrc.hs": {Icon: "\uf35e", Color: 203}, //
+ "xmobarrc": {Icon: "\uf35e", Color: 203}, //
+ "xmonad.hs": {Icon: "\uf35e", Color: 203}, //
+ "xorg.conf": {Icon: "\uf369", Color: 196}, //
+ "xsettingsd.conf": {Icon: "\uf369", Color: 196}, //
+ "yarn.lock": {Icon: "\ue6a7", Color: 74}, //
}
var extIconMap = map[string]IconProperties{
+ ".3gp": {Icon: "\uf03d", Color: 208}, //
+ ".3mf": {Icon: "\U000f01a7", Color: 102}, //
+ ".7z": {Icon: "\uf410", Color: 214}, //
+ ".aac": {Icon: "\uf001", Color: 45}, //
+ ".a": {Icon: "\ueb9c", Color: 253}, //
+ ".aiff": {Icon: "\uf001", Color: 39}, //
+ ".aif": {Icon: "\uf001", Color: 39}, //
".ai": {Icon: "\ue7b4", Color: 185}, //
".android": {Icon: "\ue70e", Color: 70}, //
+ ".ape": {Icon: "\uf001", Color: 39}, //
".apk": {Icon: "\ue70e", Color: 70}, //
+ ".app": {Icon: "\ueae8", Color: 124}, //
".apple": {Icon: "\uf179", Color: 15}, //
- ".avi": {Icon: "\uf03d", Color: 140}, //
+ ".applescript": {Icon: "\uf179", Color: 66}, //
+ ".asc": {Icon: "\uf099d", Color: 242}, //
+ ".ass": {Icon: "\U000f0a16", Color: 214}, //
+ ".astro": {Icon: "\ue6b3", Color: 197}, //
".avif": {Icon: "\uf1c5", Color: 140}, //
+ ".avi": {Icon: "\uf03d", Color: 140}, //
".avro": {Icon: "\ue60b", Color: 130}, //
".awk": {Icon: "\ue795", Color: 140}, //
- ".bash": {Icon: "\ue795", Color: 113}, //
+ ".azcli": {Icon: "\uebe8", Color: 32}, //
+ ".bak": {Icon: "\U000f006f", Color: 66}, //
".bash_history": {Icon: "\ue795", Color: 113}, //
+ ".bash": {Icon: "\ue795", Color: 113}, //
".bash_profile": {Icon: "\ue795", Color: 113}, //
".bashrc": {Icon: "\ue795", Color: 113}, //
".bat": {Icon: "\uf17a", Color: 81}, //
".bats": {Icon: "\ue795", Color: 241}, //
+ ".bazel": {Icon: "\ue63a", Color: 113}, //
+ ".bib": {Icon: "\U000f125f", Color: 185}, //
+ ".bicep": {Icon: "\ue63b", Color: 32}, //
+ ".bicepparam": {Icon: "\ue63b", Color: 103}, //
+ ".blade.php": {Icon: "\uf2f7", Color: 203}, //
+ ".blend": {Icon: "\U000f00ab", Color: 208}, //
+ ".blp": {Icon: "\U000f0ebe", Color: 68}, //
".bmp": {Icon: "\uf1c5", Color: 149}, //
- ".bz": {Icon: "\uf410", Color: 239}, //
+ ".brep": {Icon: "\U000f0eeb", Color: 101}, //
".bz2": {Icon: "\uf410", Color: 239}, //
- ".c": {Icon: "\ue61e", Color: 111}, //
- ".c++": {Icon: "\ue61d", Color: 204}, //
+ ".bz3": {Icon: "\uf410", Color: 214}, //
+ ".bz": {Icon: "\uf410", Color: 239}, //
+ ".bzl": {Icon: "\ue63a", Color: 113}, //
".cab": {Icon: "\ue70f", Color: 241}, //
+ ".cache": {Icon: "\uf49b", Color: 231}, //
+ ".cast": {Icon: "\uf03d", Color: 208}, //
+ ".cbl": {Icon: "\u2699", Color: 25}, // ⚙
".cc": {Icon: "\ue61d", Color: 204}, //
+ ".ccm": {Icon: "\ue61d", Color: 204}, //
".cfg": {Icon: "\ue615", Color: 255}, //
+ ".c++": {Icon: "\ue61d", Color: 204}, //
+ ".c": {Icon: "\ue61e", Color: 111}, //
+ ".cjs": {Icon: "\ue60c", Color: 185}, //
".class": {Icon: "\ue256", Color: 168}, //
+ ".cljc": {Icon: "\ue768", Color: 113}, //
+ ".cljd": {Icon: "\ue76a", Color: 74}, //
".clj": {Icon: "\ue768", Color: 113}, //
".cljs": {Icon: "\ue76a", Color: 74}, //
- ".cls": {Icon: "\uf034", Color: 239}, //
+ ".cls": {Icon: "\ue69b", Color: 239}, //
+ ".cmake": {Icon: "\ue615", Color: 66}, //
".cmd": {Icon: "\ue70f", Color: 239}, //
+ ".cob": {Icon: "\u2699", Color: 25}, // ⚙
+ ".cobol": {Icon: "\u2699", Color: 25}, // ⚙
".coffee": {Icon: "\uf0f4", Color: 185}, //
".conf": {Icon: "\ue615", Color: 66}, //
+ ".config.ru": {Icon: "\ue791", Color: 52}, //
".cp": {Icon: "\ue61d", Color: 74}, //
".cpio": {Icon: "\uf410", Color: 239}, //
".cpp": {Icon: "\ue61d", Color: 74}, //
- ".cs": {Icon: "\U000f031b", Color: 58}, //
+ ".cppm": {Icon: "\ue61d", Color: 74}, //
+ ".cpy": {Icon: "\u2699", Color: 25}, // ⚙
+ ".crdownload": {Icon: "\uf019", Color: 43}, //
+ ".cr": {Icon: "\ue62f", Color: 251}, //
".csh": {Icon: "\ue795", Color: 240}, //
".cshtml": {Icon: "\uf1fa", Color: 239}, //
+ ".cs": {Icon: "\U000f031b", Color: 58}, //
+ ".cson": {Icon: "\ue60b", Color: 185}, //
".csproj": {Icon: "\U000f031b", Color: 58}, //
".css": {Icon: "\ue749", Color: 75}, //
".csv": {Icon: "\uf1c3", Color: 113}, //
".csx": {Icon: "\U000f031b", Color: 58}, //
+ ".cts": {Icon: "\ue628", Color: 74}, //
+ ".cue": {Icon: "\U000f0cb9", Color: 211}, //
+ ".cuh": {Icon: "\ue64b", Color: 140}, //
+ ".cu": {Icon: "\ue64b", Color: 113}, //
".cxx": {Icon: "\ue61d", Color: 74}, //
- ".d": {Icon: "\ue7af", Color: 28}, //
+ ".cxxm": {Icon: "\ue61d", Color: 74}, //
".dart": {Icon: "\ue798", Color: 25}, //
".db": {Icon: "\uf1c0", Color: 188}, //
+ ".dconf": {Icon: "\ue706", Color: 188}, //
".deb": {Icon: "\ue77d", Color: 88}, //
+ ".desktop": {Icon: "\uf108", Color: 54}, //
+ ".d": {Icon: "\ue7af", Color: 28}, //
".diff": {Icon: "\uf440", Color: 241}, //
".djvu": {Icon: "\uf02d", Color: 241}, //
".dll": {Icon: "\ue70f", Color: 241}, //
- ".doc": {Icon: "\uf0219", Color: 26}, //
- ".docx": {Icon: "\uf0219", Color: 26}, //
+ ".doc": {Icon: "\U000f0219", Color: 26}, //
+ ".docx": {Icon: "\U000f0219", Color: 26}, //
+ ".dot": {Icon: "\U000f1049", Color: 24}, //
+ ".download": {Icon: "\uf019", Color: 43}, //
+ ".drl": {Icon: "\ue28c", Color: 217}, //
+ ".dropbox": {Icon: "\ue707", Color: 27}, //
".ds_store": {Icon: "\uf179", Color: 15}, //
".DS_store": {Icon: "\uf179", Color: 15}, //
+ ".d.ts": {Icon: "\ue628", Color: 172}, //
".dump": {Icon: "\uf1c0", Color: 188}, //
+ ".dwg": {Icon: "\U000f0eeb", Color: 101}, //
+ ".dxf": {Icon: "\U000f0eeb", Color: 101}, //
".ebook": {Icon: "\ue28b", Color: 241}, //
".ebuild": {Icon: "\uf30d", Color: 56}, //
".editorconfig": {Icon: "\ue615", Color: 241}, //
+ ".edn": {Icon: "\ue76a", Color: 74}, //
+ ".eex": {Icon: "\ue62d", Color: 140}, //
".ejs": {Icon: "\ue618", Color: 185}, //
+ ".elc": {Icon: "\ue632", Color: 97}, //
+ ".elf": {Icon: "\ueae8", Color: 124}, //
+ ".el": {Icon: "\ue632", Color: 97}, //
".elm": {Icon: "\ue62c", Color: 74}, //
+ ".eln": {Icon: "\ue632", Color: 97}, //
".env": {Icon: "\uf462", Color: 227}, //
".eot": {Icon: "\uf031", Color: 124}, //
+ ".epp": {Icon: "\ue631", Color: 214}, //
".epub": {Icon: "\ue28a", Color: 241}, //
".erb": {Icon: "\ue73b", Color: 160}, //
".erl": {Icon: "\ue7b1", Color: 163}, //
- ".ex": {Icon: "\ue62d", Color: 140}, //
".exe": {Icon: "\uf17a", Color: 81}, //
+ ".ex": {Icon: "\ue62d", Color: 140}, //
".exs": {Icon: "\ue62d", Color: 140}, //
+ ".f3d": {Icon: "\uf0eeb", Color: 101}, //
+ ".f90": {Icon: "\U000f121a", Color: 97}, //
+ ".fbx": {Icon: "\U000f01a7", Color: 102}, //
+ ".fcbak": {Icon: "\uf336", Color: 160}, //
+ ".fcmacro": {Icon: "\uf336", Color: 160}, //
+ ".fcmat": {Icon: "\uf336", Color: 160}, //
+ ".fcparam": {Icon: "\uf336", Color: 160}, //
+ ".fcscript": {Icon: "\uf336", Color: 160}, //
+ ".fcstd1": {Icon: "\uf336", Color: 160}, //
+ ".fcstd": {Icon: "\uf336", Color: 160}, //
+ ".fctb": {Icon: "\uf336", Color: 160}, //
+ ".fctl": {Icon: "\uf336", Color: 160}, //
+ ".fdmdownload": {Icon: "\uf019", Color: 43}, //
+ ".f#": {Icon: "\ue7a7", Color: 74}, //
".fish": {Icon: "\ue795", Color: 249}, //
".flac": {Icon: "\uf001", Color: 241}, //
+ ".flc": {Icon: "\uf031", Color: 255}, //
+ ".flf": {Icon: "\uf031", Color: 255}, //
".flv": {Icon: "\uf03d", Color: 241}, //
+ ".fnl": {Icon: "\ue6af", Color: 230}, //
".font": {Icon: "\uf031", Color: 241}, //
".fs": {Icon: "\ue7a7", Color: 74}, //
".fsi": {Icon: "\ue7a7", Color: 74}, //
+ ".fsscript": {Icon: "\ue7a7", Color: 74}, //
".fsx": {Icon: "\ue7a7", Color: 74}, //
+ ".gcode": {Icon: "\U000f0af4", Color: 234}, //
+ ".gd": {Icon: "\ue65f", Color: 66}, //
".gdoc": {Icon: "\uf1c2", Color: 40}, //
- ".gem": {Icon: "\ue21e", Color: 160}, //
".gemfile": {Icon: "\ue21e", Color: 160}, //
+ ".gem": {Icon: "\ue21e", Color: 160}, //
".gemspec": {Icon: "\ue21e", Color: 160}, //
".gform": {Icon: "\uf298", Color: 40}, //
".gif": {Icon: "\uf1c5", Color: 140}, //
- ".git": {Icon: "\uf1d3", Color: 202}, //
- ".gitattributes": {Icon: "\uf1d3", Color: 202}, //
- ".gitignore": {Icon: "\uf1d3", Color: 202}, //
- ".gitmodules": {Icon: "\uf1d3", Color: 202}, //
+ ".git": {Icon: "\U000f02a2", Color: 202}, //
+ ".glb": {Icon: "\uf1b2", Color: 214}, //
+ ".gnumakefile": {Icon: "\ue779", Color: 66}, //
+ ".godot": {Icon: "\ue65f", Color: 66}, //
".go": {Icon: "\ue627", Color: 74}, //
+ ".gql": {Icon: "\uf20e", Color: 199}, //
".gradle": {Icon: "\ue256", Color: 168}, //
+ ".graphql": {Icon: "\uf20e", Color: 199}, //
+ ".gresource": {Icon: "\uf362", Color: 231}, //
".groovy": {Icon: "\ue775", Color: 24}, //
".gsheet": {Icon: "\uf1c3", Color: 10}, //
".gslides": {Icon: "\uf1c4", Color: 226}, //
".guardfile": {Icon: "\ue21e", Color: 241}, //
+ ".gv": {Icon: "\U000f1049", Color: 24}, //
".gz": {Icon: "\uf410", Color: 241}, //
- ".h": {Icon: "\uf0fd", Color: 140}, //
+ ".haml": {Icon: "\ue60e", Color: 255}, //
".hbs": {Icon: "\ue60f", Color: 202}, //
+ ".hc": {Icon: "\U000f00a2", Color: 227}, //
+ ".heex": {Icon: "\ue62d", Color: 140}, //
+ ".hex": {Icon: "\U000f12a7", Color: 27}, //
+ ".hh": {Icon: "\uf0fd", Color: 140}, //
+ ".h": {Icon: "\uf0fd", Color: 140}, //
".hpp": {Icon: "\uf0fd", Color: 140}, //
+ ".hrl": {Icon: "\ue7b1", Color: 163}, //
".hs": {Icon: "\ue777", Color: 140}, //
".htm": {Icon: "\uf13b", Color: 196}, //
".html": {Icon: "\uf13b", Color: 196}, //
+ ".huff": {Icon: "\uf0858", Color: 56}, //
+ ".hurl": {Icon: "\uf0ec", Color: 198}, //
+ ".hx": {Icon: "\ue666", Color: 208}, //
".hxx": {Icon: "\uf0fd", Color: 140}, //
+ ".icalendar": {Icon: "\uf073", Color: 18}, //
+ ".ical": {Icon: "\uf073", Color: 18}, //
".ico": {Icon: "\uf1c5", Color: 185}, //
+ ".ics": {Icon: "\uf073", Color: 18}, //
+ ".ifb": {Icon: "\uf073", Color: 18}, //
+ ".ifc": {Icon: "\U000f0eeb", Color: 101}, //
+ ".ige": {Icon: "\U000f0eeb", Color: 101}, //
+ ".iges": {Icon: "\U000f0eeb", Color: 101}, //
+ ".igs": {Icon: "\U000f0eeb", Color: 101}, //
".image": {Icon: "\uf1c5", Color: 185}, //
+ ".img": {Icon: "\ue271", Color: 181}, //
".iml": {Icon: "\ue7b5", Color: 239}, //
+ ".import": {Icon: "\uf0c6", Color: 255}, //
+ ".info": {Icon: "\uf129", Color: 230}, //
".ini": {Icon: "\uf17a", Color: 81}, //
+ ".ino": {Icon: "\uf34b", Color: 73}, //
".ipynb": {Icon: "\ue606", Color: 214}, //
".iso": {Icon: "\ue271", Color: 239}, //
+ ".ixx": {Icon: "\ue61d", Color: 74}, //
".j2c": {Icon: "\uf1c5", Color: 239}, //
".j2k": {Icon: "\uf1c5", Color: 239}, //
".jad": {Icon: "\ue256", Color: 168}, //
".jar": {Icon: "\ue256", Color: 168}, //
".java": {Icon: "\ue256", Color: 168}, //
- ".jfi": {Icon: "\uf1c5", Color: 241}, //
".jfif": {Icon: "\uf1c5", Color: 241}, //
+ ".jfi": {Icon: "\uf1c5", Color: 241}, //
".jif": {Icon: "\uf1c5", Color: 241}, //
".jl": {Icon: "\ue624", Color: 241}, //
".jmd": {Icon: "\uf48a", Color: 74}, //
".jp2": {Icon: "\uf1c5", Color: 241}, //
- ".jpe": {Icon: "\uf1c5", Color: 241}, //
".jpeg": {Icon: "\uf1c5", Color: 241}, //
+ ".jpe": {Icon: "\uf1c5", Color: 241}, //
".jpg": {Icon: "\uf1c5", Color: 241}, //
".jpx": {Icon: "\uf1c5", Color: 241}, //
".js": {Icon: "\ue74e", Color: 185}, //
+ ".json5": {Icon: "\ue60b", Color: 185}, //
+ ".jsonc": {Icon: "\ue60b", Color: 185}, //
".json": {Icon: "\ue60b", Color: 185}, //
".jsx": {Icon: "\ue7ba", Color: 45}, //
+ ".jwmrc": {Icon: "\uf35b", Color: 32}, //
".jxl": {Icon: "\uf1c5", Color: 241}, //
+ ".kbx": {Icon: "\uf0bc4", Color: 243}, //
+ ".kdb": {Icon: "\uf23e", Color: 71}, //
+ ".kdbx": {Icon: "\uf23e", Color: 71}, //
+ ".kdenlive": {Icon: "\uf33c", Color: 110}, //
+ ".kdenlivetitle": {Icon: "\uf33c", Color: 110}, //
+ ".kicad_dru": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_mod": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_pcb": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_prl": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_pro": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_sch": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_sym": {Icon: "\uf34c", Color: 231}, //
+ ".kicad_wks": {Icon: "\uf34c", Color: 231}, //
+ ".ko": {Icon: "\uf17c", Color: 253}, //
+ ".kpp": {Icon: "\uf33d", Color: 201}, //
+ ".kra": {Icon: "\uf33d", Color: 201}, //
+ ".krz": {Icon: "\uf33d", Color: 201}, //
".ksh": {Icon: "\ue795", Color: 241}, //
".kt": {Icon: "\ue634", Color: 99}, //
".kts": {Icon: "\ue634", Color: 99}, //
- ".latex": {Icon: "\uf034", Color: 241}, //
+ ".latex": {Icon: "\ue69b", Color: 241}, //
+ ".lck": {Icon: "\ue672", Color: 250}, //
+ ".leex": {Icon: "\ue62d", Color: 140}, //
".less": {Icon: "\ue758", Color: 54}, //
+ ".lff": {Icon: "\uf031", Color: 255}, //
".lhs": {Icon: "\ue777", Color: 140}, //
".license": {Icon: "\U000f0219", Color: 185}, //
+ ".liquid": {Icon: "\ue670", Color: 106}, //
".localized": {Icon: "\uf179", Color: 15}, //
".lock": {Icon: "\uf023", Color: 241}, //
- ".log": {Icon: "\uf18d", Color: 188}, //
+ ".log": {Icon: "\uf4ed", Color: 188}, //
+ ".lrc": {Icon: "\U000f0a16", Color: 214}, //
+ ".luac": {Icon: "\ue620", Color: 74}, //
".lua": {Icon: "\ue620", Color: 74}, //
- ".lz": {Icon: "\uf410", Color: 241}, //
+ ".luau": {Icon: "\ue620", Color: 74}, //
".lz4": {Icon: "\uf410", Color: 241}, //
".lzh": {Icon: "\uf410", Color: 241}, //
+ ".lz": {Icon: "\uf410", Color: 241}, //
".lzma": {Icon: "\uf410", Color: 241}, //
".lzo": {Icon: "\uf410", Color: 241}, //
- ".m": {Icon: "\ue61e", Color: 111}, //
- ".mm": {Icon: "\ue61d", Color: 111}, //
+ ".m3u8": {Icon: "\U000f0cb9", Color: 211}, //
+ ".m3u": {Icon: "\U000f0cb9", Color: 211}, //
".m4a": {Icon: "\uf001", Color: 239}, //
+ ".m4v": {Icon: "\uf03d", Color: 208}, //
+ ".magnet": {Icon: "\uf076", Color: 124}, //
+ ".makefile": {Icon: "\ue779", Color: 66}, //
".markdown": {Icon: "\uf48a", Color: 74}, //
+ ".material": {Icon: "\uf0509", Color: 163}, //
+ ".md5": {Icon: "\uf0565", Color: 103}, //
".md": {Icon: "\uf48a", Color: 74}, //
".mdx": {Icon: "\uf48a", Color: 74}, //
+ ".m": {Icon: "\ue61e", Color: 111}, //
+ ".mint": {Icon: "\uf032a", Color: 108}, //
".mjs": {Icon: "\ue74e", Color: 185}, //
- ".mk": {Icon: "\ue795", Color: 241}, //
".mkd": {Icon: "\uf48a", Color: 74}, //
+ ".mk": {Icon: "\ue795", Color: 241}, //
".mkv": {Icon: "\uf03d", Color: 241}, //
+ ".ml": {Icon: "\ue67a", Color: 166}, //
+ ".mli": {Icon: "\ue67a", Color: 166}, //
+ ".mm": {Icon: "\ue61d", Color: 111}, //
".mobi": {Icon: "\ue28b", Color: 241}, //
+ ".mo": {Icon: "\u221e", Color: 135}, // ∞
+ ".mojo": {Icon: "\uf06d", Color: 196}, //
".mov": {Icon: "\uf03d", Color: 241}, //
".mp3": {Icon: "\uf001", Color: 241}, //
".mp4": {Icon: "\uf03d", Color: 241}, //
+ ".mpp": {Icon: "\ue61d", Color: 74}, //
+ ".msf": {Icon: "\uf370", Color: 33}, //
".msi": {Icon: "\ue70f", Color: 241}, //
+ ".mts": {Icon: "\ue628", Color: 74}, //
".mustache": {Icon: "\ue60f", Color: 241}, //
+ ".nfo": {Icon: "\uf129", Color: 230}, //
+ ".nim": {Icon: "\ue677", Color: 220}, //
".nix": {Icon: "\uf313", Color: 111}, //
".node": {Icon: "\U000f0399", Color: 197}, //
".npmignore": {Icon: "\ue71e", Color: 197}, //
+ ".nswag": {Icon: "\ue60b", Color: 112}, //
+ ".nu": {Icon: "\u003e", Color: 36}, // >
+ ".obj": {Icon: "\U000f01a7", Color: 102}, //
".odp": {Icon: "\uf1c4", Color: 241}, //
".ods": {Icon: "\uf1c3", Color: 241}, //
".odt": {Icon: "\uf1c2", Color: 241}, //
".ogg": {Icon: "\uf001", Color: 241}, //
".ogv": {Icon: "\uf03d", Color: 241}, //
+ ".o": {Icon: "\ueae8", Color: 124}, //
+ ".opus": {Icon: "\U000f0223", Color: 208}, //
+ ".org": {Icon: "\ue633", Color: 73}, //
".otf": {Icon: "\uf031", Color: 241}, //
+ ".out": {Icon: "\ueae8", Color: 124}, //
".part": {Icon: "\uf43a", Color: 241}, //
".patch": {Icon: "\uf440", Color: 241}, //
+ ".pck": {Icon: "\uf487", Color: 66}, //
".pdf": {Icon: "\uf1c1", Color: 124}, //
".php": {Icon: "\ue73d", Color: 61}, //
".pl": {Icon: "\ue769", Color: 74}, //
+ ".pls": {Icon: "\U000f0cb9", Color: 211}, //
+ ".ply": {Icon: "\U000f01a7", Color: 102}, //
+ ".pm": {Icon: "\ue769", Color: 74}, //
".png": {Icon: "\uf1c5", Color: 241}, //
+ ".po": {Icon: "\U000f05ca", Color: 31}, //
+ ".pot": {Icon: "\U000f05ca", Color: 31}, //
+ ".pp": {Icon: "\ue631", Color: 214}, //
".ppt": {Icon: "\uf1c4", Color: 241}, //
".pptx": {Icon: "\uf1c4", Color: 241}, //
+ ".prisma": {Icon: "\ue684", Color: 62}, //
".procfile": {Icon: "\ue21e", Color: 241}, //
+ ".pro": {Icon: "\ue7a1", Color: 179}, //
".properties": {Icon: "\ue60b", Color: 185}, //
".ps1": {Icon: "\ue795", Color: 241}, //
+ ".psb": {Icon: "\ue7b8", Color: 74}, //
+ ".psd1": {Icon: "\uf0a0a", Color: 68}, //
".psd": {Icon: "\ue7b8", Color: 241}, //
+ ".psm1": {Icon: "\uf0a0a", Color: 68}, //
+ ".pub": {Icon: "\uf0dd6", Color: 222}, //
+ ".pxd": {Icon: "\ue606", Color: 39}, //
+ ".pxi": {Icon: "\ue606", Color: 39}, //
".pxm": {Icon: "\uf1c5", Color: 241}, //
- ".py": {Icon: "\ue606", Color: 214}, //
".pyc": {Icon: "\ue606", Color: 214}, //
- ".r": {Icon: "\uf25d", Color: 68}, //
+ ".pyd": {Icon: "\ue606", Color: 222}, //
+ ".py": {Icon: "\ue606", Color: 214}, //
+ ".pyi": {Icon: "\ue606", Color: 214}, //
+ ".pyo": {Icon: "\ue606", Color: 222}, //
+ ".pyw": {Icon: "\ue606", Color: 39}, //
+ ".pyx": {Icon: "\ue606", Color: 39}, //
+ ".qm": {Icon: "\U000f05ca", Color: 31}, //
+ ".qml": {Icon: "\uf375", Color: 77}, //
+ ".qrc": {Icon: "\uf375", Color: 77}, //
+ ".qss": {Icon: "\uf375", Color: 77}, //
+ ".query": {Icon: "\ue21c", Color: 107}, //
".rakefile": {Icon: "\ue21e", Color: 160}, //
+ ".rake": {Icon: "\ue791", Color: 52}, //
".rar": {Icon: "\uf410", Color: 241}, //
".razor": {Icon: "\uf1fa", Color: 81}, //
".rb": {Icon: "\ue21e", Color: 160}, //
@@ -245,43 +563,96 @@ var extIconMap = map[string]IconProperties{
".rdoc": {Icon: "\uf48a", Color: 74}, //
".rds": {Icon: "\uf25d", Color: 68}, //
".readme": {Icon: "\uf48a", Color: 74}, //
+ ".res": {Icon: "\ue688", Color: 167}, //
+ ".resi": {Icon: "\ue688", Color: 204}, //
+ ".r": {Icon: "\uf25d", Color: 68}, //
".rlib": {Icon: "\ue7a8", Color: 216}, //
".rmd": {Icon: "\uf48a", Color: 74}, //
".rpm": {Icon: "\ue7bb", Color: 52}, //
+ ".rproj": {Icon: "\uf05c6", Color: 29}, //
".rs": {Icon: "\ue7a8", Color: 216}, //
".rspec": {Icon: "\ue21e", Color: 160}, //
".rspec_parallel": {Icon: "\ue21e", Color: 160}, //
".rspec_status": {Icon: "\ue21e", Color: 160}, //
".rss": {Icon: "\uf09e", Color: 130}, //
".rtf": {Icon: "\U000f0219", Color: 241}, //
- ".ru": {Icon: "\ue21e", Color: 160}, //
".rubydoc": {Icon: "\ue73b", Color: 160}, //
+ ".ru": {Icon: "\ue21e", Color: 160}, //
".sass": {Icon: "\ue603", Color: 169}, //
+ ".sbt": {Icon: "\ue737", Color: 167}, //
+ ".scad": {Icon: "\uf34e", Color: 220}, //
".scala": {Icon: "\ue737", Color: 74}, //
+ ".sc": {Icon: "\ue737", Color: 167}, //
+ ".scm": {Icon: "\uf0627", Color: 255}, //
".scss": {Icon: "\ue749", Color: 204}, //
- ".sh": {Icon: "\ue795", Color: 239}, //
+ ".sha1": {Icon: "\uf0565", Color: 103}, //
+ ".sha224": {Icon: "\uf0565", Color: 103}, //
+ ".sha256": {Icon: "\uf0565", Color: 103}, //
+ ".sha384": {Icon: "\uf0565", Color: 103}, //
+ ".sha512": {Icon: "\uf0565", Color: 103}, //
".shell": {Icon: "\ue795", Color: 239}, //
+ ".sh": {Icon: "\ue795", Color: 239}, //
+ ".sig": {Icon: "\u03bb", Color: 166}, // λ
+ ".signature": {Icon: "\u03bb", Color: 166}, // λ
+ ".skp": {Icon: "\U000f0eeb", Color: 101}, //
+ ".sldasm": {Icon: "\U000f0eeb", Color: 101}, //
+ ".sldprt": {Icon: "\U000f0eeb", Color: 101}, //
".slim": {Icon: "\ue73b", Color: 160}, //
".sln": {Icon: "\ue70c", Color: 39}, //
+ ".slvs": {Icon: "\U000f0eeb", Color: 101}, //
+ ".sml": {Icon: "\u03bb", Color: 166}, // λ
".so": {Icon: "\uf17c", Color: 241}, //
+ ".sol": {Icon: "\ue656", Color: 74}, //
+ ".spec.js": {Icon: "\uf499", Color: 185}, //
+ ".spec.jsx": {Icon: "\uf499", Color: 45}, //
+ ".spec.ts": {Icon: "\uf499", Color: 74}, //
+ ".spec.tsx": {Icon: "\uf499", Color: 26}, //
".sql": {Icon: "\uf1c0", Color: 188}, //
".sqlite3": {Icon: "\ue7c4", Color: 25}, //
- ".sty": {Icon: "\uf034", Color: 239}, //
+ ".sqlite": {Icon: "\ue7c4", Color: 25}, //
+ ".srt": {Icon: "\U000f0a16", Color: 214}, //
+ ".ssa": {Icon: "\U000f0a16", Color: 214}, //
+ ".ste": {Icon: "\U000f0eeb", Color: 101}, //
+ ".step": {Icon: "\U000f0eeb", Color: 101}, //
+ ".stl": {Icon: "\U000f01a7", Color: 102}, //
+ ".stp": {Icon: "\U000f0eeb", Color: 101}, //
+ ".strings": {Icon: "\U000f05ca", Color: 31}, //
+ ".sty": {Icon: "\ue69b", Color: 239}, //
".styl": {Icon: "\ue600", Color: 148}, //
".stylus": {Icon: "\ue600", Color: 148}, //
+ ".sub": {Icon: "\U000f0a16", Color: 214}, //
+ ".sublime": {Icon: "\ue7aa", Color: 166}, //
+ ".suo": {Icon: "\ue70c", Color: 98}, //
".svelte": {Icon: "\ue697", Color: 208}, //
".svg": {Icon: "\uf1c5", Color: 241}, //
+ ".svh": {Icon: "\U000f035b", Color: 28}, //
+ ".sv": {Icon: "\U000f035b", Color: 28}, //
".swift": {Icon: "\ue755", Color: 208}, //
".tar": {Icon: "\uf410", Color: 241}, //
".taz": {Icon: "\uf410", Color: 241}, //
- ".tbz": {Icon: "\uf410", Color: 241}, //
+ ".tbc": {Icon: "\uf06d3", Color: 25}, //
".tbz2": {Icon: "\uf410", Color: 241}, //
- ".tex": {Icon: "\uf034", Color: 79}, //
+ ".tbz": {Icon: "\uf410", Color: 241}, //
+ ".tcl": {Icon: "\uf06d3", Color: 25}, //
+ ".templ": {Icon: "\ueac4", Color: 178}, //
+ ".terminal": {Icon: "\uf489", Color: 34}, //
+ ".test.js": {Icon: "\uf499", Color: 185}, //
+ ".test.jsx": {Icon: "\uf499", Color: 45}, //
+ ".test.ts": {Icon: "\uf499", Color: 74}, //
+ ".test.tsx": {Icon: "\uf499", Color: 26}, //
+ ".tex": {Icon: "\ue69b", Color: 79}, //
+ ".tf": {Icon: "\ue69a", Color: 93}, //
+ ".tfvars": {Icon: "\uf15b", Color: 93}, //
".tgz": {Icon: "\uf410", Color: 241}, //
+ ".t": {Icon: "\ue769", Color: 74}, //
".tiff": {Icon: "\uf1c5", Color: 241}, //
".tlz": {Icon: "\uf410", Color: 241}, //
- ".toml": {Icon: "\ue615", Color: 241}, //
+ ".tmux": {Icon: "\uebc8", Color: 34}, //
+ ".toml": {Icon: "\ue6b2", Color: 241}, //
".torrent": {Icon: "\ue275", Color: 76}, //
+ ".tres": {Icon: "\ue65f", Color: 66}, //
+ ".tscn": {Icon: "\ue65f", Color: 66}, //
+ ".tsconfig": {Icon: "\ue772", Color: 208}, //
".ts": {Icon: "\ue628", Color: 74}, //
".tsv": {Icon: "\uf1c3", Color: 241}, //
".tsx": {Icon: "\ue7ba", Color: 74}, //
@@ -289,30 +660,55 @@ var extIconMap = map[string]IconProperties{
".twig": {Icon: "\ue61c", Color: 241}, //
".txt": {Icon: "\uf15c", Color: 241}, //
".txz": {Icon: "\uf410", Color: 241}, //
+ ".typoscript": {Icon: "\ue772", Color: 208}, //
".tz": {Icon: "\uf410", Color: 241}, //
".tzo": {Icon: "\uf410", Color: 241}, //
+ ".ui": {Icon: "\uf2d0", Color: 17}, //
+ ".vala": {Icon: "\ue69e", Color: 91}, //
+ ".vhd": {Icon: "\U000f035b", Color: 28}, //
+ ".vhdl": {Icon: "\U000f035b", Color: 28}, //
+ ".vh": {Icon: "\U000f035b", Color: 28}, //
+ ".v": {Icon: "\U000f035b", Color: 28}, //
".video": {Icon: "\uf03d", Color: 241}, //
".vim": {Icon: "\ue62b", Color: 28}, //
+ ".vsh": {Icon: "\ue6ac", Color: 67}, //
+ ".vsix": {Icon: "\ue70c", Color: 98}, //
".vue": {Icon: "\U000f0844", Color: 113}, //
".war": {Icon: "\ue256", Color: 168}, //
+ ".wasm": {Icon: "\ue6a1", Color: 62}, //
".wav": {Icon: "\uf001", Color: 241}, //
+ ".webmanifest": {Icon: "\ue60b", Color: 185}, //
".webm": {Icon: "\uf03d", Color: 241}, //
+ ".webpack": {Icon: "\uf072b", Color: 74}, //
".webp": {Icon: "\uf1c5", Color: 241}, //
".windows": {Icon: "\uf17a", Color: 81}, //
- ".woff": {Icon: "\uf031", Color: 241}, //
+ ".wma": {Icon: "\uf001", Color: 39}, //
".woff2": {Icon: "\uf031", Color: 241}, //
+ ".woff": {Icon: "\uf031", Color: 241}, //
+ ".wrl": {Icon: "\U000f01a7", Color: 102}, //
+ ".wrz": {Icon: "\U000f01a7", Color: 102}, //
+ ".wvc": {Icon: "\uf001", Color: 39}, //
+ ".wv": {Icon: "\uf001", Color: 39}, //
+ ".xaml": {Icon: "\uf0673", Color: 56}, //
+ ".xcf": {Icon: "\uf338", Color: 240}, //
+ ".xcplayground": {Icon: "\ue755", Color: 166}, //
+ ".xcstrings": {Icon: "\U000f05ca", Color: 31}, //
".xhtml": {Icon: "\uf13b", Color: 196}, //
+ ".x": {Icon: "\ue691", Color: 111}, //
".xls": {Icon: "\uf1c3", Color: 34}, //
".xlsx": {Icon: "\uf1c3", Color: 34}, //
+ ".xm": {Icon: "\ue691", Color: 74}, //
".xml": {Icon: "\uf121", Color: 160}, //
+ ".xpi": {Icon: "\ueae6", Color: 17}, //
".xul": {Icon: "\uf121", Color: 166}, //
".xz": {Icon: "\uf410", Color: 241}, //
".yaml": {Icon: "\uf481", Color: 160}, //
".yml": {Icon: "\uf481", Color: 160}, //
+ ".zig": {Icon: "\ue6a9", Color: 172}, //
".zip": {Icon: "\uf410", Color: 241}, //
".zsh": {Icon: "\ue795", Color: 241}, //
- ".zsh-theme": {Icon: "\ue795", Color: 241}, //
".zshrc": {Icon: "\ue795", Color: 241}, //
+ ".zsh-theme": {Icon: "\ue795", Color: 241}, //
".zst": {Icon: "\uf410", Color: 241}, //
}
diff --git a/pkg/gui/presentation/icons/git_icons.go b/pkg/gui/presentation/icons/git_icons.go
index 5a7c0afc61b..55ada2e512d 100644
--- a/pkg/gui/presentation/icons/git_icons.go
+++ b/pkg/gui/presentation/icons/git_icons.go
@@ -19,10 +19,20 @@ var (
)
var remoteIcons = map[string]string{
- "github.com": "\ue709", //
- "bitbucket.org": "\ue703", //
- "gitlab.com": "\uf296", //
- "dev.azure.com": "\U000f0805", //
+ "github.com": "\ue709", //
+ "bitbucket.org": "\ue703", //
+ "gitlab.com": "\uf296", //
+ "dev.azure.com": "\U000f0805", //
+ "codeberg.org": "\uf330", //
+ "git.FreeBSD.org": "\uf30c", //
+ "gitlab.archlinux.org": "\uf303", //
+ "gitlab.freedesktop.org": "\uf360", //
+ "gitlab.gnome.org": "\uf361", //
+ "gnu.org": "\ue779", //
+ "invent.kde.org": "\uf373", //
+ "kernel.org": "\uf31a", //
+ "salsa.debian.org": "\uf306", //
+ "sr.ht": "\uf1db", //
}
func patchGitIconsForNerdFontsV2() {
diff --git a/pkg/gui/presentation/status.go b/pkg/gui/presentation/status.go
index d3686510eb4..b3b2100673f 100644
--- a/pkg/gui/presentation/status.go
+++ b/pkg/gui/presentation/status.go
@@ -2,6 +2,7 @@ package presentation
import (
"fmt"
+ "time"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@@ -24,7 +25,10 @@ func FormatStatus(
status := ""
if currentBranch.IsRealBranch() {
- status += ColoredBranchStatus(currentBranch, itemOperation, tr, userConfig) + " "
+ status += BranchStatus(currentBranch, itemOperation, tr, time.Now(), userConfig)
+ if status != "" {
+ status += " "
+ }
}
if workingTreeState != enums.REBASE_MODE_NONE {
@@ -40,7 +44,7 @@ func FormatStatus(
}
repoName = fmt.Sprintf("%s(%s%s)", repoName, icon, style.FgCyan.Sprint(linkedWorktreeName))
}
- status += fmt.Sprintf("%s → %s ", repoName, name)
+ status += fmt.Sprintf("%s → %s", repoName, name)
return status
}
diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go
index cf3176f7505..969e1aadabb 100644
--- a/pkg/gui/pty.go
+++ b/pkg/gui/pty.go
@@ -15,8 +15,8 @@ import (
"github.com/samber/lo"
)
-func (gui *Gui) desiredPtySize() *pty.Winsize {
- width, height := gui.Views.Main.Size()
+func (gui *Gui) desiredPtySize(view *gocui.View) *pty.Winsize {
+ width, height := view.Size()
return &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}
}
@@ -25,11 +25,12 @@ func (gui *Gui) onResize() error {
gui.Mutexes.PtyMutex.Lock()
defer gui.Mutexes.PtyMutex.Unlock()
- for _, ptmx := range gui.viewPtmxMap {
+ for viewName, ptmx := range gui.viewPtmxMap {
// TODO: handle resizing properly: we need to actually clear the main view
// and re-read the output from our pty. Or we could just re-run the original
// command from scratch
- if err := pty.Setsize(ptmx, gui.desiredPtySize()); err != nil {
+ view, _ := gui.g.View(viewName)
+ if err := pty.Setsize(ptmx, gui.desiredPtySize(view)); err != nil {
return utils.WrapError(err)
}
}
@@ -44,7 +45,7 @@ func (gui *Gui) onResize() error {
// pseudo-terminal meaning we'll get the behaviour we want from the underlying
// command.
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
- width, _ := gui.Views.Main.Size()
+ width, _ := view.Size()
pager := gui.git.Config.GetPager(width)
externalDiffCommand := gui.Config.GetUserConfig().Git.Paging.ExternalDiffCommand
@@ -69,7 +70,7 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
var ptmx *os.File
start := func() (*exec.Cmd, io.Reader) {
var err error
- ptmx, err = pty.StartWithSize(cmd, gui.desiredPtySize())
+ ptmx, err = pty.StartWithSize(cmd, gui.desiredPtySize(view))
if err != nil {
gui.c.Log.Error(err)
}
diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go
index acdb2067220..0f2f2c7049c 100644
--- a/pkg/gui/recent_repos_panel.go
+++ b/pkg/gui/recent_repos_panel.go
@@ -8,12 +8,7 @@ import (
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
// so that we can open the same repo via the 'recent repos' menu
func (gui *Gui) updateRecentRepoList() error {
- isBareRepo, err := gui.git.Status.IsBareRepo()
- if err != nil {
- return err
- }
-
- if isBareRepo {
+ if gui.git.Status.IsBareRepo() {
// we could totally do this but it would require storing both the git-dir and the
// worktree in our recent repos list, which is a change that would need to be
// backwards compatible
diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go
index 9a127f3623f..c1cfe09cbcd 100644
--- a/pkg/gui/services/custom_commands/handler_creator.go
+++ b/pkg/gui/services/custom_commands/handler_creator.go
@@ -292,7 +292,15 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
if strings.TrimSpace(output) == "" {
output = self.c.Tr.EmptyOutput
}
- return self.c.Alert(cmdStr, output)
+
+ title := cmdStr
+ if customCommand.OutputTitle != "" {
+ title, err = resolveTemplate(customCommand.OutputTitle)
+ if err != nil {
+ return err
+ }
+ }
+ return self.c.Alert(title, output)
}
return nil
diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go
index 65009fb78b7..c5014ad729e 100644
--- a/pkg/gui/test_mode.go
+++ b/pkg/gui/test_mode.go
@@ -38,7 +38,7 @@ func (gui *Gui) handleTestMode() {
gui.PopupHandler.(*popup.PopupHandler).SetToastFunc(
func(message string, kind types.ToastKind) { toastChan <- message })
- test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan, toastChan: toastChan})
+ test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan, toastChan: toastChan, headless: Headless()})
gui.g.Update(func(*gocui.Gui) error {
return gocui.ErrQuit
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index d5017307820..c6885d7175c 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -67,6 +67,7 @@ type IGuiCommon interface {
CurrentContext() Context
CurrentStaticContext() Context
CurrentSideContext() Context
+ CurrentPopupContexts() []Context
IsCurrentContext(Context) bool
// TODO: replace the above context-based methods with just using Context() e.g. replace PushContext() with Context().Push()
Context() IContextMgr
@@ -159,6 +160,7 @@ const (
type CreateMenuOptions struct {
Title string
+ Prompt string // a message that will be displayed above the menu options
Items []*MenuItem
HideCancel bool
ColumnAlignment []utils.Alignment
@@ -216,6 +218,30 @@ type DisabledReason struct {
ShowErrorInPanel bool
}
+type MenuWidget int
+
+const (
+ MenuWidgetNone MenuWidget = iota
+ MenuWidgetRadioButtonSelected
+ MenuWidgetRadioButtonUnselected
+ MenuWidgetCheckboxSelected
+ MenuWidgetCheckboxUnselected
+)
+
+func MakeMenuRadioButton(value bool) MenuWidget {
+ if value {
+ return MenuWidgetRadioButtonSelected
+ }
+ return MenuWidgetRadioButtonUnselected
+}
+
+func MakeMenuCheckBox(value bool) MenuWidget {
+ if value {
+ return MenuWidgetCheckboxSelected
+ }
+ return MenuWidgetCheckboxUnselected
+}
+
type MenuItem struct {
Label string
@@ -231,6 +257,12 @@ type MenuItem struct {
// item, as opposed to having to navigate to it
Key Key
+ // A widget to show in front of the menu item. Supported widget types are
+ // checkboxes and radio buttons,
+ // This only handles the rendering of the widget; the behavior needs to be
+ // provided by the client.
+ Widget MenuWidget
+
// The tooltip will be displayed upon highlighting the menu item
Tooltip string
@@ -281,6 +313,8 @@ type Model struct {
// we're on a detached head because we're rebasing or bisecting.
CheckedOutBranch string
+ MainBranches *git_commands.MainBranches
+
// for displaying suggestions while typing in a file name
FilesTrie *patricia.Trie
diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go
index 63c759eb6ae..70458c16f14 100644
--- a/pkg/gui/types/context.go
+++ b/pkg/gui/types/context.go
@@ -39,6 +39,18 @@ type ParentContexter interface {
GetParentContext() (Context, bool)
}
+type NeedsRerenderOnWidthChangeLevel int
+
+const (
+ // view doesn't render differently when its width changes
+ NEEDS_RERENDER_ON_WIDTH_CHANGE_NONE NeedsRerenderOnWidthChangeLevel = iota
+ // view renders differently when its width changes. An example is a view
+ // that truncates long lines to the view width, e.g. the branches view
+ NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES
+ // view renders differently only when the screen mode changes
+ NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES
+)
+
type IBaseContext interface {
HasKeybindings
ParentContexter
@@ -60,8 +72,11 @@ type IBaseContext interface {
// determined independently.
HasControlledBounds() bool
- // true if the view needs to be rerendered when its width changes
- NeedsRerenderOnWidthChange() bool
+ // to what extent the view needs to be rerendered when its width changes
+ NeedsRerenderOnWidthChange() NeedsRerenderOnWidthChangeLevel
+
+ // true if the view needs to be rerendered when its height changes
+ NeedsRerenderOnHeightChange() bool
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
// no title will be set
@@ -71,6 +86,7 @@ type IBaseContext interface {
AddKeybindingsFn(KeybindingsFn)
AddMouseKeybindingsFn(MouseKeybindingsFn)
+ ClearAllBindingsFn()
// This is a bit of a hack at the moment: we currently only set an onclick function so that
// our list controller can come along and wrap it in a list-specific click handler.
@@ -114,12 +130,16 @@ type ISearchableContext interface {
Context
ISearchHistoryContext
+ // These are all implemented by SearchTrait
SetSearchString(string)
GetSearchString() string
ClearSearchString()
IsSearching() bool
IsSearchableContext()
RenderSearchStatus(int, int)
+
+ // This must be implemented by each concrete context. Return nil if not searching the model.
+ ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition
}
type DiffableContext interface {
@@ -146,6 +166,7 @@ type IListContext interface {
FocusLine()
IsListContext() // used for type switch
RangeSelectEnabled() bool
+ RenderOnlyVisibleLines() bool
}
type IPatchExplorerContext interface {
@@ -168,6 +189,8 @@ type IViewTrait interface {
SetRangeSelectStart(yIdx int)
CancelRangeSelect()
SetViewPortContent(content string)
+ SetViewPortContentAndClearEverythingElse(content string)
+ SetContentLineCount(lineCount int)
SetContent(content string)
SetFooter(value string)
SetOriginX(value int)
diff --git a/pkg/gui/views.go b/pkg/gui/views.go
index 9fd775764d3..9a4fa0a479a 100644
--- a/pkg/gui/views.go
+++ b/pkg/gui/views.go
@@ -92,6 +92,7 @@ func (gui *Gui) createAllViews() error {
(*mapping.viewPtr).FrameRunes = frameRunes
(*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
+ (*mapping.viewPtr).InactiveViewSelBgColor = theme.GocuiInactiveViewSelectedLineBgColor
}
gui.Views.Options.Frame = false
diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go
deleted file mode 100644
index 002143b282e..00000000000
--- a/pkg/i18n/chinese.go
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
-
-本翻译文件中的词语的翻译参考了 https://github.com/progit/progit2-zh/blob/master/TRANSLATION_NOTES.asc。
-下方的术语对照表是对其的补充。
-
-Translation in this file refer to https://github.com/progit/progit2-zh/blob/master/TRANSLATION_NOTES.asc.
-Glossary below is a supplement of that documentation.
-
-Glossary 术语对照表
-
-change 更改
-fixup 修正
-reset 重置
-
-*/
-
-package i18n
-
-const chineseIntroPopupMessage = `
-感谢使用 lazygit!你真的太棒了。下面几点你可能会感兴趣:
-
- 1) 观看此视频,快速了解 lazygit 的功能:
- https://youtu.be/CPLdltN7wgE
-
- 2) 记得看看最新发行说明:
- https://github.com/jesseduffield/lazygit/releases
-
- 3) 使用 git 说明你是一位程序员!你可以和我们一起让 lazygit 变得更好。
- 考虑为本项目做些贡献吧:
- https://github.com/jesseduffield/lazygit
- 你也可以直接赞助,并告诉我哪里需要改进,点右下角的捐赠按钮就好了。
- 哪怕只是给仓库点个星星也很棒!
-`
-
-// exporting this so we can use it in tests
-func chineseTranslationSet() TranslationSet {
- return TranslationSet{
- NotEnoughSpace: "没有足够的空间来渲染面板",
- DiffTitle: "差异",
- FilesTitle: "文件",
- BranchesTitle: "分支",
- CommitsTitle: "提交",
- StashTitle: "贮藏",
- UnstagedChanges: `未暂存更改`,
- StagedChanges: `已暂存更改`,
- MainTitle: "主要",
- StagingTitle: "正在暂存",
- MergingTitle: "正在合并",
- NormalTitle: "正常",
- CommitSummary: "提交信息",
- CredentialsUsername: "用户名",
- CredentialsPassword: "密码",
- CredentialsPassphrase: "输入 SSH 密钥的密码",
- PassUnameWrong: "密码 和/或 用户名错误",
- Commit: "提交更改",
- AmendLastCommit: "修补最后一次提交",
- AmendLastCommitTitle: "修补最后一次提交",
- SureToAmend: "您确定要修补上一次提交吗?之后您可以从提交面板更改提交消息。",
- NoCommitToAmend: "没有需要提交的修补。",
- CommitChangesWithEditor: "提交更改(使用编辑器编辑提交信息)",
- StatusTitle: "状态",
- Menu: "菜单",
- Execute: "执行",
- Stage: "切换暂存状态",
- ToggleStagedAll: "切换所有文件的暂存状态",
- ToggleTreeView: "切换文件树视图",
- OpenMergeTool: "打开外部合并工具 (git mergetool)",
- Refresh: "刷新",
- Push: "推送",
- Pull: "拉取",
- Scroll: "滚动",
- MergeConflictsTitle: "合并冲突",
- Checkout: "检出",
- NoChangedFiles: "没有更改过文件",
- SoftReset: "软重置",
- AlreadyCheckedOutBranch: "您已经检出至此分支",
- SureForceCheckout: "您确定要强制检出吗?您将丢失所有本地更改",
- ForceCheckoutBranch: "强制检出分支",
- BranchName: "分支名称",
- NewBranchNameBranchOff: "新分支名称(基于 {{.branchName}})",
- CantDeleteCheckOutBranch: "您不能删除已检出的分支!",
- ForceDeleteBranchMessage: "{{.selectedBranchName}} 还没有被完全合并。您确定要删除它吗?",
- RebaseBranch: "将已检出的分支变基到该分支",
- CantRebaseOntoSelf: "您不能将分支变基到其自身",
- CantMergeBranchIntoItself: "您不能将分支合并到其自身",
- ForceCheckout: "强制检出",
- CheckoutByName: "按名称检出",
- NewBranch: "新分支",
- NoBranchesThisRepo: "此仓库中没有分支",
- CommitWithoutMessageErr: "您必须编写提交消息才能进行提交",
- CloseCancel: "关闭",
- Confirm: "确认",
- Close: "关闭",
- Quit: "退出",
- NoCommitsThisBranch: "该分支没有提交",
- CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
- Fixup: "修正(fixup)",
- SureFixupThisCommit: "您确定要“修正”此提交吗?它将合并到下面的提交中",
- SureSquashThisCommit: "您确定要将这个提交压缩到下面的提交中吗?",
- Squash: "压缩",
- PickCommitTooltip: "选择提交(变基过程中)",
- RevertCommit: "还原提交",
- Reword: "改写提交",
- DropCommit: "删除提交",
- MoveDownCommit: "下移提交",
- MoveUpCommit: "上移提交",
- EditCommitTooltip: "编辑提交",
- AmendCommitTooltip: "用已暂存的更改来修补提交",
- RewordCommitEditor: "使用编辑器重命名提交",
- Error: "错误",
- PickHunk: "选中区块",
- PickAllHunks: "选中所有区块",
- Undo: "撤销",
- UndoReflog: "(通过 reflog)撤销「实验功能」",
- RedoReflog: "(通过 reflog)重做「实验功能」",
- Pop: "应用并删除",
- Drop: "删除",
- Apply: "应用",
- NoStashEntries: "没有贮藏条目",
- StashDrop: "删除贮藏",
- SureDropStashEntry: "您确定要删除此贮藏条目吗?",
- StashPop: "应用并删除贮藏",
- SurePopStashEntry: "您确定要应用并删除此贮藏条目吗?",
- StashApply: "应用贮藏",
- SureApplyStashEntry: "您确定要应用此贮藏条目?",
- NoTrackedStagedFilesStash: "没有可以贮藏的已跟踪/暂存文件",
- StashChanges: "贮藏更改",
- RenameStash: "Rename stash",
- RenameStashPrompt: "Rename stash: {{.stashName}}",
- OpenConfig: "打开配置文件",
- EditConfig: "编辑配置文件",
- ForcePush: "强制推送",
- ForcePushPrompt: "您的分支已与远程分支不同。按‘esc’取消,或‘enter’强制推送.",
- ForcePushDisabled: "您的分支已与远程分支不同, 并且您已经禁用了强行推送",
- CheckForUpdate: "检查更新",
- CheckingForUpdates: "正在检查更新…",
- OnLatestVersionErr: "已是最新版本",
- MajorVersionErr: "新版本 ({{.newVersion}}) 与当前版本 ({{.currentVersion}}) 相比,具有非向后兼容的更改",
- CouldNotFindBinaryErr: "在 {{.url}} 处找不到任何二进制文件",
- MergeToolTitle: "合并工具",
- MergeToolPrompt: "确定要打开 `git mergetool` 吗?",
- IntroPopupMessage: chineseIntroPopupMessage,
- GitconfigParseErr: `由于存在未加引号的'\'字符,因此 Gogit 无法解析您的 gitconfig 文件。删除它们应该可以解决问题。`,
- EditFile: `编辑文件`,
- OpenFile: `打开文件`,
- IgnoreFile: `添加到 .gitignore`,
- RefreshFiles: `刷新文件`,
- Merge: `合并到当前检出的分支`,
- ConfirmQuit: `您确定要退出吗?`,
- SwitchRepo: `切换到最近的仓库`,
- AllBranchesLogGraph: `显示所有分支的日志`,
- UnsupportedGitService: `不支持的 git 服务`,
- CreatePullRequest: `创建抓取请求`,
- CopyPullRequestURL: `将抓取请求 URL 复制到剪贴板`,
- NoBranchOnRemote: `该分支在远程上不存在. 您需要先将其推送到远程.`,
- Fetch: `抓取`,
- NoAutomaticGitFetchTitle: `无法自动进行 "git fetch"`,
- NoAutomaticGitFetchBody: `Lazygit 不能在私人仓库中使用 "git fetch"; 请在文件面板中使用 'f' 手动运行 "git fetch"`,
- FileEnter: `暂存单个 块/行 用于文件, 或 折叠/展开 目录`,
- FileStagingRequirements: `只能暂存跟踪文件的单独行`,
- StageSelectionTooltip: `切换行暂存状态`,
- DiscardSelection: `取消变更 (git reset)`,
- ToggleRangeSelect: `切换拖动选择`,
- ToggleSelectHunk: `切换选择区块`,
- ToggleSelectionForPatch: `添加/移除 行到补丁`,
- ToggleStagingView: `切换到其他面板`,
- ReturnToFilesPanel: `返回文件面板`,
- FastForward: `从上游快进此分支`,
- FastForwarding: "抓取并快进",
- FoundConflictsTitle: "自动合并失败",
- ViewMergeRebaseOptions: "查看 合并/变基 选项",
- NotMergingOrRebasing: "您目前既不进行变基也不进行合并",
- RecentRepos: "最近的仓库",
- MergeOptionsTitle: "合并选项",
- RebaseOptionsTitle: "变基选项",
- CommitSummaryTitle: "提交讯息",
- LocalBranchesTitle: "分支页面",
- SearchTitle: "搜索",
- TagsTitle: "标签页面",
- MenuTitle: "菜单",
- RemotesTitle: "远程页面",
- RemoteBranchesTitle: "远程分支",
- PatchBuildingTitle: "构建补丁中",
- InformationTitle: "信息",
- SecondaryTitle: "次要",
- ReflogCommitsTitle: "Reflog 页面",
- GlobalTitle: "全局键绑定",
- ConflictsResolved: "已解决所有冲突。是否继续?",
- ConfirmMerge: "您确定要将分支 {{.selectedBranch}} 合并到 {{.checkedOutBranch}} 吗?",
- FwdNoUpstream: "此分支没有上游,无法快进",
- FwdNoLocalUpstream: "此分支的远程未在本地注册,无法快进",
- FwdCommitsToPush: "此分支带有尚未推送的提交,无法快进",
- ErrorOccurred: "发生错误!请在以下位置创建 issue",
- NoRoom: "空间不足",
- YouAreHere: "您在这里",
- RewordNotSupported: "当前不支持交互式重新基准化时的重新措词提交",
- CherryPickCopy: "复制提交(拣选)",
- PasteCommits: "粘贴提交(拣选)",
- SureCherryPick: "您确定要将选中的提交进行拣选到这个分支吗?",
- CherryPick: "拣选 (Cherry-Pick)",
- Donate: "捐助",
- AskQuestion: "提问咨询",
- PrevLine: "选择上一行",
- NextLine: "选择下一行",
- PrevHunk: "选择上一个区块",
- NextHunk: "选择下一个区块",
- PrevConflict: "选择上一个冲突",
- NextConflict: "选择下一个冲突",
- SelectPrevHunk: "选择顶部块",
- SelectNextHunk: "选择底部块",
- ScrollDown: "向下滚动",
- ScrollUp: "向上滚动",
- ScrollUpMainWindow: "向上滚动主面板",
- ScrollDownMainWindow: "向下滚动主面板",
- AmendCommitTitle: "修改提交",
- AmendCommitPrompt: "您确定要使用暂存文件来修改此提交吗?",
- DropCommitTitle: "删除提交",
- DropCommitPrompt: "您确定要删除此提交吗?",
- PullingStatus: "正在拉取",
- PushingStatus: "正在推送",
- FetchingStatus: "正在抓取",
- SquashingStatus: "正在压缩",
- FixingStatus: "正在修正",
- DeletingStatus: "正在删除",
- MovingStatus: "正在移动",
- RebasingStatus: "正在变基",
- AmendingStatus: "正在修改",
- CherryPickingStatus: "正在拣选",
- UndoingStatus: "正在撤销",
- RedoingStatus: "正在重做",
- CheckingOutStatus: "长子检出",
- CommittingStatus: "正在提交",
- CommitFiles: "提交文件",
- ViewItemFiles: "查看提交的文件",
- CommitFilesTitle: "提交文件",
- CheckoutCommitFileTooltip: "检出文件",
- DiscardOldFileChangeTooltip: "放弃对此文件的提交更改",
- DiscardFileChangesTitle: "放弃文件更改",
- DiscardFileChangesPrompt: "您确定要舍弃此提交对该文件的更改吗?如果此文件是在此提交中创建的,它将被删除",
- DisabledForGPG: "该功能不适用于使用 GPG 的用户",
- CreateRepo: "当前目录不在 git 仓库中。是否在此目录创建一个新的 git 仓库?(y/n): ",
- AutoStashTitle: "自动存储?",
- AutoStashPrompt: "您必须隐藏并弹出更改以使更改生效。自动执行?(enter/esc)",
- StashPrefix: "自动隐藏更改 ",
- Discard: "查看'放弃更改'选项",
- Cancel: "取消",
- DiscardAllChanges: "放弃所有更改",
- DiscardUnstagedChanges: "放弃未暂存的变更",
- DiscardAllChangesToAllFiles: "清空工作区",
- DiscardAnyUnstagedChanges: "丢弃未暂存的变更",
- DiscardUntrackedFiles: "丢弃未跟踪的文件",
- HardReset: "硬重置",
- ViewResetOptions: `查看重置选项`,
- CreateFixupCommit: `为此提交创建修正`,
- SquashAboveCommitsTooltip: `压缩在所选提交之上的所有“fixup!”提交(自动压缩)`,
- CreateFixupCommitTooltip: `创建修正提交`,
- ExecuteCustomCommand: "执行自定义命令",
- CustomCommand: "自定义命令:",
- CommitChangesWithoutHook: "提交更改而无需预先提交钩子",
- SkipHookPrefixNotConfigured: "您尚未配置用于跳过钩子的提交消息前缀。请在您的配置中设置 `git.skipHookPrefix ='WIP'`",
- ResetTo: `重置为`,
- PressEnterToReturn: "按下 Enter 键返回 lazygit",
- ViewStashOptions: "查看贮藏选项",
- StashAllChanges: "将所有更改加入贮藏",
- StashAllChangesKeepIndex: "将已暂存的更改加入贮藏",
- StashOptions: "贮藏选项",
- NotARepository: "错误:必须在 git 仓库中运行",
- Jump: "跳到面板",
- ScrollLeftRight: "左右滚动",
- ScrollLeft: "向左滚动",
- ScrollRight: "向右滚动",
- DiscardPatch: "丢弃补丁",
- DiscardPatchConfirm: "您一次只能通过一个提交或贮藏条目构建补丁。需要放弃当前补丁吗?",
- CantPatchWhileRebasingError: "处于合并或变基状态时,您无法构建修补程序或运行修补程序命令",
- ToggleAddToPatch: "补丁中包含的切换文件",
- ViewPatchOptions: "查看自定义补丁选项",
- PatchOptionsTitle: "补丁选项",
- NoPatchError: "尚未创建补丁。你可以在提交中的文件上按下“空格”或使用“回车”添加其中的特定行以开始构建补丁",
- EnterCommitFile: "输入文件以将所选行添加到补丁中(或切换目录折叠)",
- ExitCustomPatchBuilder: `退出逐行模式`,
- EnterUpstream: `以这种格式输入上游:'<远程仓库> <分支名称>'`,
- InvalidUpstream: "上游格式无效,格式应当为:' '",
- ReturnToRemotesList: `返回远程仓库列表`,
- NewRemote: `添加新的远程仓库`,
- NewRemoteName: `新远程仓库名称:`,
- NewRemoteUrl: `新远程仓库 URL:`,
- EditRemoteName: `输入远程仓库 {{.remoteName}} 的新名称:`,
- EditRemoteUrl: `输入远程仓库 {{.remoteName}} 的新 URL:`,
- RemoveRemote: `删除远程`,
- RemoveRemotePrompt: "您确定要删除远程仓库吗?",
- DeleteRemoteBranch: "删除远程分支",
- DeleteRemoteBranchMessage: "您确定要删除远程分支吗?",
- SetUpstream: "设置为检出分支的上游",
- SetAsUpstreamTooltip: "设置为检出分支的上游",
- SetUpstreamTitle: "设置上游分支",
- SetUpstreamMessage: "您确定要将 {{.checkedOut}} 的上游分支设置为 {{.selected}} 吗?",
- EditRemoteTooltip: "编辑远程仓库",
- TagCommit: "标签提交",
- TagMenuTitle: "创建标签",
- TagNameTitle: "标签名称",
- TagMessageTitle: "标签消息",
- AnnotatedTag: "附注标签",
- LightweightTag: "轻量标签",
- PushTagTitle: "将 {{.tagName}} 推送到远程仓库:",
- PushTag: "推送标签",
- NewTag: "创建标签",
- FetchRemoteTooltip: "抓取远程仓库",
- FetchingRemoteStatus: "抓取远程仓库中",
- CheckoutCommit: "检出提交",
- SureCheckoutThisCommit: "您确定要检出此提交吗?",
- GitFlowOptions: "显示 git-flow 选项",
- NotAGitFlowBranch: "这似乎不是 git flow 分支",
- NewGitFlowBranchPrompt: "新的 {{.branchType}} 名称:",
- IgnoreTracked: "忽略跟踪文件",
- IgnoreTrackedPrompt: "您确定要忽略已跟踪的文件吗?",
- ViewResetToUpstreamOptions: "查看上游重置选项",
- NextScreenMode: "下一屏模式(正常/半屏/全屏)",
- PrevScreenMode: "上一屏模式",
- StartSearch: "开始搜索",
- Panel: "面板",
- Keybindings: "按键绑定",
- RenameBranch: "重命名分支",
- NewBranchNamePrompt: "输入分支的新名称",
- RenameBranchWarning: "该分支正在跟踪远程仓库。此操作将仅会重命名本地分支名称,而不会重命名远程分支的名称。确定继续?",
- OpenKeybindingsMenu: "打开菜单",
- ResetCherryPick: "重置已拣选(复制)的提交",
- NextTab: "下一个标签",
- PrevTab: "上一个标签",
- CantUndoWhileRebasing: "进行基础调整时无法撤消",
- CantRedoWhileRebasing: "变基时无法重做",
- MustStashWarning: "将补丁拉出到索引中需要存储和取消存储所做的更改。如果出现问题,您将可以从存储中访问文件。继续?",
- MustStashTitle: "必须保存进度",
- ConfirmationTitle: "确认面板",
- PrevPage: "上一页",
- NextPage: "下一页",
- GotoTop: "滚动到顶部",
- GotoBottom: "滚动到底部",
- FilteringBy: "过滤依据",
- ResetInParentheses: "(重置)",
- OpenFilteringMenu: "查看按路径过滤选项",
- FilterBy: "过滤",
- ExitFilterMode: "停止按路径过滤",
- FilterPathOption: "输入要过滤的路径",
- EnterFileName: "输入路径:",
- FilteringMenuTitle: "正在过滤",
- MustExitFilterModeTitle: "命令不可用",
- MustExitFilterModePrompt: "命令在过滤模式下不可用。退出过滤模式?",
- Diff: "差异",
- EnterRefToDiff: "输入 ref 以 diff",
- EnterRefName: "输入 ref:",
- ExitDiffMode: "退出差异模式",
- DiffingMenuTitle: "正在 diff",
- SwapDiff: "反向 diff",
- ViewDiffingOptions: "打开 diff 菜单",
- // 实际视图 (actual view) 是附加视图 (extras view),未来,我打算为附加视图提供更多选项卡,但现在,上面的文本只需要提及“命令日志”这个部分
- OpenCommandLogMenu: "打开命令日志菜单",
- ShowingGitDiff: "显示输出:",
- CopyCommitHashToClipboard: "将提交的 hash 复制到剪贴板",
- CopyCommitMessageToClipboard: "将提交消息复制到剪贴板",
- CopyBranchNameToClipboard: "将分支名称复制到剪贴板",
- CopyPathToClipboard: "将文件名复制到剪贴板",
- CopySelectedTextToClipboard: "将选中文本复制到剪贴板",
- CommitPrefixPatternError: "提交前缀模式错误",
- NoFilesStagedTitle: "没有暂存文件",
- NoFilesStagedPrompt: "您尚未暂存任何文件。提交所有文件?",
- BranchNotFoundTitle: "找不到分支",
- BranchNotFoundPrompt: "找不到分支。创建一个新分支命名为:",
- DiscardChangeTitle: "取消暂存选中的行",
- DiscardChangePrompt: "您确定要删除所选的行(git reset)吗?这是不可逆的。\n要禁用此对话框,请将 'gui.skipDiscardChangeWarning' 的配置键设置为 true",
- CreateNewBranchFromCommit: "从提交创建新分支",
- BuildingPatch: "正在构建补丁",
- ViewCommits: "查看提交",
- MinGitVersionError: "Git 版本必须至少为 2.20(即从 2018 年开始的版本)。请更新 git。或者在 https://github.com/jesseduffield/lazygit/issues 上提出一个问题,以使 lazygit 更加向后兼容。",
- RunningCustomCommandStatus: "正在运行自定义命令",
- SubmoduleStashAndReset: "存放未提交的子模块更改和更新",
- AndResetSubmodules: "和重置子模块",
- EnterSubmoduleTooltip: "输入子模块",
- CopySubmoduleNameToClipboard: "将子模块名称复制到剪贴板",
- RemoveSubmodule: "删除子模块",
- RemoveSubmodulePrompt: "您确定要删除子模块 '%s' 及其对应的目录吗?这是不可逆的。",
- ResettingSubmoduleStatus: "正在重置子模块",
- NewSubmoduleName: "新的子模块名称:",
- NewSubmoduleUrl: "新的子模块 URL:",
- NewSubmodulePath: "新的子模块路径:",
- NewSubmodule: "添加新的子模块",
- AddingSubmoduleStatus: "添加子模块",
- UpdateSubmoduleUrl: "更新子模块 '%s' 的 URL",
- UpdatingSubmoduleUrlStatus: "更新 URL 中",
- EditSubmoduleUrl: "更新子模块 URL",
- InitializingSubmoduleStatus: "正在初始化子模块",
- InitSubmoduleTooltip: "初始化子模块",
- SubmoduleUpdateTooltip: "更新子模块",
- UpdatingSubmoduleStatus: "正在更新子模块",
- BulkInitSubmodules: "批量初始化子模块",
- BulkUpdateSubmodules: "批量更新子模块",
- BulkDeinitSubmodules: "批量反初始化子模块",
- ViewBulkSubmoduleOptions: "查看批量子模块选项",
- BulkSubmoduleOptions: "批量子模块选项",
- RunningCommand: "运行命令",
- SubCommitsTitle: "子提交",
- SubmodulesTitle: "子模块",
- NavigationTitle: "列表面板导航",
- SuggestionsCheatsheetTitle: "意见建议",
- SuggestionsTitle: "意见建议 (点击 %s 以聚焦)",
- ExtrasTitle: "附加",
- PushingTagStatus: "推送标签",
- PullRequestURLCopiedToClipboard: "抓取请求网址已复制到剪贴板",
- CommitMessageCopiedToClipboard: "提交消息复制到剪贴板",
- CopiedToClipboard: "复制到剪贴板",
- ErrCannotEditDirectory: "无法编辑目录:您只能编辑单个文件",
- ErrStageDirWithInlineMergeConflicts: "无法 暂存/取消暂存 包含具有内联合并冲突的文件的目录。请先解决合并冲突",
- ErrRepositoryMovedOrDeleted: "找不到仓库。它可能已被移动或删除 ¯\\_(ツ)_/¯",
- CommandLog: "命令日志",
- ToggleShowCommandLog: "切换 显示/隐藏 命令日志",
- FocusCommandLog: "焦点命令日志",
- CommandLogHeader: "您可以通过按 '%s' 隐藏或集中显示该面板,或使用 `gui.showCommandLog: false`\n将其永久隐藏在您的配置中",
- RandomTip: "随机小提示",
- SelectParentCommitForMerge: "选择父提交进行合并",
- ToggleWhitespaceInDiffView: "切换是否在差异视图中显示空白字符差异",
- IncreaseContextInDiffView: "扩大差异视图中显示的上下文范围",
- DecreaseContextInDiffView: "缩小差异视图中显示的上下文范围",
- CreatePullRequestOptions: "创建抓取请求选项",
- DefaultBranch: "默认分支",
- SelectBranch: "选择分支",
- SelectConfigFile: "选择配置文件",
- NoConfigFileFoundErr: "找不到配置文件",
- LoadingFileSuggestions: "正在加载文件建议",
- LoadingCommits: "正在加载提交",
- MustSpecifyOriginError: "指定分支时,必须同时指定远程",
- GitOutput: "Git 输出:",
- GitCommandFailed: "Git 命令执行失败。查看命令日志了解详情 (使用 %s 打开)",
- AbortTitle: "放弃 %s",
- AbortPrompt: "您确定要放弃当前 %s 吗?",
- OpenLogMenu: "打开日志菜单",
- LogMenuTitle: "提交日志选项",
- ToggleShowGitGraphAll: "切换显示完整 git 分支图 (向 `git log` 命令传入 `--all` 选项)",
- ShowGitGraph: "显示 git 分支图",
- SortCommits: "提交排序",
- CantChangeContextSizeError: "无法在补丁构建模式下更改上下文,因为我们在发布该功能时懒得支持它。 如果你真的想要这么做,请告诉我们!",
- OpenCommitInBrowser: "在浏览器中打开提交",
- ViewBisectOptions: "查看二分查找选项",
- Actions: Actions{
- // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
- CheckoutCommit: "检出提交",
- CheckoutTag: "检出标签",
- CheckoutBranch: "检出分支",
- ForceCheckoutBranch: "强制检出分支",
- Merge: "合并",
- RebaseBranch: "变基分支",
- RenameBranch: "重命名分支",
- CreateBranch: "建立分支",
- CherryPick: "(拣选) 粘贴提交",
- CheckoutFile: "检出文件",
- DiscardOldFileChange: "放弃旧文件更改",
- SquashCommitDown: "向下压缩提交",
- FixupCommit: "修正提交",
- RewordCommit: "改写提交",
- DropCommit: "删除提交",
- EditCommit: "编辑提交",
- AmendCommit: "修改提交",
- RevertCommit: "还原提交",
- CreateFixupCommit: "创建修正提交",
- SquashAllAboveFixupCommits: "压缩以上所有的修正提交",
- CreateLightweightTag: "创建轻量标签",
- CreateAnnotatedTag: "创建附注标签",
- CopyCommitMessageToClipboard: "将提交消息复制到剪贴板",
- MoveCommitUp: "上移提交",
- MoveCommitDown: "下移提交",
- CustomCommand: "自定义命令",
- DiscardAllChangesInDirectory: "丢弃目录中的所有更改",
- DiscardUnstagedChangesInDirectory: "丢弃目录中未暂存的更改",
- DiscardAllChangesInFile: "丢弃文件中的所有更改",
- DiscardAllUnstagedChangesInFile: "丢弃文件中所有未暂存的更改",
- StageFile: "暂存文件",
- UnstageFile: "取消暂存文件",
- UnstageAllFiles: "取消暂存所有文件",
- StageAllFiles: "暂存所有文件",
- IgnoreExcludeFile: "忽略文件",
- Commit: "提交 (Commit)",
- EditFile: "编辑文件",
- Push: "推送 (Push)",
- Pull: "拉取 (Pull)",
- OpenFile: "打开文件",
- StashAllChanges: "贮藏所有更改",
- StashStagedChanges: "贮藏暂存的更改",
- GitFlowFinish: "git flow 结果",
- GitFlowStart: "git flow 开始",
- CopyToClipboard: "复制到剪贴板",
- CopySelectedTextToClipboard: "将选中文本复制到剪贴板",
- RemovePatchFromCommit: "从提交中删除补丁",
- MovePatchToSelectedCommit: "将补丁移动到选定的提交",
- MovePatchIntoIndex: "将补丁移到索引",
- MovePatchIntoNewCommit: "将补丁移到新提交中",
- DeleteRemoteBranch: "删除远程分支",
- SetBranchUpstream: "设置分支上游",
- AddRemote: "添加远程",
- RemoveRemote: "移除远程",
- UpdateRemote: "更新远程",
- ApplyPatch: "应用补丁",
- Stash: "贮藏 (Stash)",
- RenameStash: "Rename stash",
- RemoveSubmodule: "删除子模块",
- ResetSubmodule: "重置子模块",
- AddSubmodule: "添加子模块",
- UpdateSubmoduleUrl: "更新子模块 URL",
- InitialiseSubmodule: "初始化子模块",
- BulkInitialiseSubmodules: "批量初始化子模块",
- BulkUpdateSubmodules: "批量更新子模块",
- BulkDeinitialiseSubmodules: "批量取消初始化子模块",
- UpdateSubmodule: "更新子模块",
- PushTag: "推送标签",
- NukeWorkingTree: "Nuke 工作树",
- DiscardUnstagedFileChanges: "放弃未暂存的文件更改",
- RemoveUntrackedFiles: "删除未跟踪的文件",
- SoftReset: "软重置",
- MixedReset: "混合重置",
- HardReset: "硬重置",
- FastForwardBranch: "快进分支",
- Undo: "撤销",
- Redo: "重做",
- CopyPullRequestURL: "复制拉取请求 URL",
- OpenMergeTool: "打开合并工具",
- OpenCommitInBrowser: "在浏览器中打开提交",
- OpenPullRequest: "在浏览器中打开拉取请求",
- StartBisect: "开始二分查找 (Bisect)",
- ResetBisect: "重置二分查找",
- BisectSkip: "二分查找跳过",
- BisectMark: "二分查找标记",
- },
- Bisect: Bisect{
- Mark: "将 %s 标记为 %s",
- MarkStart: "将 %s 标记为 %s (start bisect)",
- SkipCurrent: "跳过 %s",
- ResetTitle: "重置 'git bisect'",
- ResetPrompt: "您确定要重置 'git bisect' 吗?",
- ResetOption: "重置二分查找",
- BisectMenuTitle: "二分查找",
- CompleteTitle: "二分查找完成",
- CompletePrompt: "二分查找完成!以下提交引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?",
- CompletePromptIndeterminate: "二分查找完成!一些提交被跳过了,所以下列提交中的任何一个都可能引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?",
- },
- }
-}
diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go
deleted file mode 100644
index 4db19c43128..00000000000
--- a/pkg/i18n/dutch.go
+++ /dev/null
@@ -1,335 +0,0 @@
-package i18n
-
-func dutchTranslationSet() TranslationSet {
- return TranslationSet{
- NotEnoughSpace: "Niet genoeg ruimte om de panelen te renderen",
- DiffTitle: "Diff",
- FilesTitle: "Bestanden",
- BranchesTitle: "Branches",
- CommitsTitle: "Commits",
- StashTitle: "Stash",
- UnstagedChanges: "Unstaged wijzigingen",
- StagedChanges: "Staged wijzigingen",
- MainTitle: "Hoofd",
- StagingTitle: "Staging",
- NormalTitle: "Normaal",
- CommitSummary: "Commitbericht",
- CredentialsUsername: "Gebruikersnaam",
- CredentialsPassword: "Wachtwoord",
- CredentialsPassphrase: "Voer een wachtwoordzin in voor de SSH-sleutel",
- PassUnameWrong: "Wachtwoord en/of gebruikersnaam verkeerd",
- Commit: "Commit veranderingen",
- AmendLastCommit: "Wijzig laatste commit",
- AmendLastCommitTitle: "Wijzig laatste commit",
- SureToAmend: "Weet je zeker dat je de laatste commit wilt wijzigen? U kunt het commit-bericht wijzigen vanuit het commits-paneel.",
- NoCommitToAmend: "Er is geen commits om te wijzigen.",
- CommitChangesWithEditor: "Commit veranderingen met de git editor",
- StatusTitle: "Status",
- Menu: "Menu",
- Execute: "Uitvoeren",
- Stage: "Toggle staged",
- ToggleStagedAll: "Toggle staged alle",
- Refresh: "Verversen",
- Push: "Push",
- Pull: "Pull",
- Scroll: "Scroll",
- FilterStagedFiles: "Show only staged files",
- FilterUnstagedFiles: "Show only unstaged files",
- ResetFilter: "Reset commit file state filter",
- MergeConflictsTitle: "Merge conflicten",
- Checkout: "Uitchecken",
- SoftReset: "Zacht reset",
- AlreadyCheckedOutBranch: "Je hebt deze branch al uitgecheckt",
- SureForceCheckout: "Weet je zeker dat je het uitchecken wil forceren? Al je lokale verandering zullen worden verwijdert",
- ForceCheckoutBranch: "Forceer uitchecken op deze branch",
- BranchName: "Branch naam",
- NewBranchNameBranchOff: "Nieuw branch naam (Branch is afgeleid van '{{.branchName}}')",
- CantDeleteCheckOutBranch: "Je kan een uitgecheckte branch niet verwijderen!",
- ForceDeleteBranchMessage: "Weet je zeker dat je branch '{{.selectedBranchName}}' geforceerd wil verwijderen?",
- RebaseBranch: "Rebase branch",
- CantRebaseOntoSelf: "Je kan niet een branch rebasen op zichzelf",
- CantMergeBranchIntoItself: "Je kan niet een branch in zichzelf mergen",
- ForceCheckout: "Forceer checkout",
- CheckoutByName: "Uitchecken bij naam",
- NewBranch: "Nieuwe branch",
- NoBranchesThisRepo: "Geen branches voor deze repo",
- CommitWithoutMessageErr: "Je kan geen commit maken zonder commit bericht",
- CloseCancel: "Sluiten",
- Confirm: "Bevestig",
- Close: "Sluiten",
- Quit: "Quit",
- CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
- Fixup: "Fixup",
- SureFixupThisCommit: "Weet je zeker dat je fixup wil uitvoeren op deze commit? De commit hieronder zol worden squashed in deze",
- SureSquashThisCommit: "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?",
- Squash: "Squash",
- PickCommitTooltip: "Kies commit (wanneer midden in rebase)",
- RevertCommit: "Commit ongedaan maken",
- Reword: "Hernoem commit",
- DropCommit: "Verwijder commit",
- MoveDownCommit: "Verplaats commit 1 naar beneden",
- MoveUpCommit: "Verplaats commit 1 naar boven",
- EditCommitTooltip: "Wijzig commit",
- AmendCommitTooltip: "Wijzig commit met staged veranderingen",
- RewordCommitEditor: "Hernoem commit met editor",
- NoCommitsThisBranch: "Geen commits in deze branch",
- Error: "Foutmelding",
- PickHunk: "Kies stuk",
- PickAllHunks: "Kies beide stukken",
- Undo: "Ongedaan maken",
- UndoReflog: "Ongedaan maken (via reflog) (experimenteel)",
- RedoReflog: "Redo (via reflog) (experimenteel)",
- Pop: "Pop",
- Drop: "Laten vallen",
- Apply: "Toepassen",
- NoStashEntries: "Geen stash items",
- StashDrop: "Stash laten vallen",
- SureDropStashEntry: "Weet je het zeker dat je deze stash entry wil laten vallen?",
- StashPop: "Stash pop",
- SurePopStashEntry: "Weet je zeker dat je deze stash entry wil poppen?",
- StashApply: "Stash toepassen",
- SureApplyStashEntry: "Weet je zeker dat je deze stash entry wil toepassen?",
- NoTrackedStagedFilesStash: "Je hebt geen tracked/staged bestanden om te laten stashen",
- StashChanges: "Stash veranderingen",
- RenameStash: "Rename stash",
- RenameStashPrompt: "Rename stash: {{.stashName}}",
- NoChangedFiles: "Geen veranderde bestanden",
- OpenConfig: "Open config bestand",
- EditConfig: "Verander config bestand",
- ForcePush: "Forceer push",
- ForcePushPrompt: "Jouw branch is afgeweken van de remote branch. Druk 'esc' om te annuleren, of 'enter' om geforceert te pushen.",
- ForcePushDisabled: "Your branch has diverged from the remote branch and you've disabled force pushing",
- CheckForUpdate: "Check voor updates",
- CheckingForUpdates: "Zoeken naar updates...",
- OnLatestVersionErr: "Je hebt al de laatste versie",
- MajorVersionErr: "Nieuwe versie ({{.newVersion}}) is niet backwards compatibele vergeleken met de huidige versie ({{.currentVersion}})",
- CouldNotFindBinaryErr: "Kon geen binary vinden op {{.url}}",
- IntroPopupMessage: "Bedankt voor het gebruik maken van lazygit! 2 dingen die je moet weten:\n\n1) Als je meer van lazygit zijn features wilt leren bekijk dan deze video:\n https://youtu.be/CPLdltN7wgE\n\n2) Als je git gebruikt, ben je een programmeur! Met jouw hulp kunnen we lazygit verbeteren, dus overweeg om een donateur te worden en mee te doen aan het plezier op\n https://github.com/jesseduffield/lazygit",
- GitconfigParseErr: `Gogit kon je gitconfig bestand niet goed parsen door de aanwezigheid van losstaande '\' tekens. Het weghalen van deze tekens zou het probleem moeten oplossen. `,
- EditFile: `Verander bestand`,
- OpenFile: `Open bestand`,
- IgnoreFile: `Voeg toe aan .gitignore`,
- RefreshFiles: `Refresh bestanden`,
- Merge: `Merge in met huidige checked out branch`,
- ConfirmQuit: `Weet je zeker dat je dit programma wil sluiten?`,
- SwitchRepo: "Wissel naar een recente repo",
- AllBranchesLogGraph: `Alle logs van de branch laten zien`,
- UnsupportedGitService: `Niet-ondersteunde git-service`,
- CreatePullRequest: `Maak een pull-request`,
- CopyPullRequestURL: `Kopieer de URL van het pull-verzoek naar het klembord`,
- NoBranchOnRemote: `Deze branch bestaat niet op de remote. U moet het eerst naar de remote pushen.`,
- Fetch: `Fetch`,
- NoAutomaticGitFetchTitle: `Geen automatische git fetch`,
- NoAutomaticGitFetchBody: `Lazygit kan niet "git fetch" uitvoeren in een privé repository, gebruik f in het branches paneel om "git fetch" manueel uit te voeren`,
- FileEnter: `Stage individuele hunks/lijnen`,
- FileStagingRequirements: `Kan alleen individuele lijnen stagen van getrackte bestanden met onstaged veranderingen`,
- StageSelectionTooltip: `Toggle lijnen staged / unstaged`,
- DiscardSelection: `Verwijdert change (git reset)`,
- ToggleRangeSelect: `Toggle drag selecteer`,
- ToggleSelectHunk: `Toggle selecteer hunk`,
- ToggleSelectionForPatch: `Voeg toe/verwijder lijn(en) in patch`,
- ToggleStagingView: `Ga naar een ander paneel`,
- ReturnToFilesPanel: `Ga terug naar het bestanden paneel`,
- FastForward: `Fast-forward deze branch vanaf zijn upstream`,
- FastForwarding: "Fast-forwarding",
- FoundConflictsTitle: "Conflicten!",
- ViewMergeRebaseOptions: "Bekijk merge/rebase opties",
- NotMergingOrRebasing: "Je bent momenteel niet aan het rebasen of mergen",
- RecentRepos: "Recente repositories",
- MergeOptionsTitle: "Merge opties",
- RebaseOptionsTitle: "Rebase opties",
- CommitSummaryTitle: "Commit bericht",
- LocalBranchesTitle: "Branches",
- SearchTitle: "Zoek",
- TagsTitle: "Tags",
- MenuTitle: "Menu",
- RemotesTitle: "Remotes",
- RemoteBranchesTitle: "Remote branches",
- PatchBuildingTitle: "Patch bouwen",
- InformationTitle: "Informatie",
- SecondaryTitle: "Secondary",
- ReflogCommitsTitle: "Reflog",
- GlobalTitle: "Globale sneltoetsen",
- ConflictsResolved: "Alle merge conflicten zijn opgelost. Wilt je verder gaan?",
- MergingTitle: "Mergen",
- ConfirmMerge: "Weet je zeker dat je '{{.selectedBranch}}' in '{{.checkedOutBranch}}' wil mergen?",
- FwdNoUpstream: "Kan niet de branch vooruitspoelen zonder upstream",
- FwdCommitsToPush: "Je kan niet vooruitspoelen als de branch geen nieuwe commits heeft",
- ErrorOccurred: "Er is iets fout gegaan! Zou je hier een issue aan willen maken",
- NoRoom: "Niet genoeg ruimte",
- YouAreHere: "JE BENT HIER",
- RewordNotSupported: "Herformatteren van commits in interactief rebasen is nog niet ondersteund",
- CherryPickCopy: "Kopieer commit (cherry-pick)",
- PasteCommits: "Plak commits (cherry-pick)",
- SureCherryPick: "Weet je zeker dat je de gekopieerde commits naar deze branch wil cherry-picken?",
- CherryPick: "Cherry-Pick",
- Donate: "Doneer",
- PrevLine: "Selecteer de vorige lijn",
- NextLine: "Selecteer de volgende lijn",
- PrevHunk: "Selecteer de vorige hunk",
- NextHunk: "Selecteer de volgende hunk",
- PrevConflict: "Selecteer voorgaand conflict",
- NextConflict: "Selecteer volgende conflict",
- SelectPrevHunk: "Selecteer bovenste hunk",
- SelectNextHunk: "Selecteer onderste hunk",
- ScrollDown: "Scroll omlaag",
- ScrollUp: "Scroll omhoog",
- ScrollUpMainWindow: "Scroll naar beneden vanaf hoofdpaneel",
- ScrollDownMainWindow: "Scroll naar beneden vanaf hoofdpaneel",
- AmendCommitTitle: "Commit wijzigen",
- AmendCommitPrompt: "Weet je zeker dat je deze commit wil wijzigen met de vorige staged bestanden?",
- DropCommitTitle: "Verwijder commit",
- DropCommitPrompt: "Weet je zeker dat je deze commit wil verwijderen?",
- PullingStatus: "Pullen",
- PushingStatus: "Pushen",
- FetchingStatus: "Fetchen",
- SquashingStatus: "Squashen",
- FixingStatus: "Fixing up",
- DeletingStatus: "Verwijderen",
- MovingStatus: "Verplaatsen",
- RebasingStatus: "Rebasen",
- AmendingStatus: "Wijzigen",
- CherryPickingStatus: "Cherry-picken",
- UndoingStatus: "Ongedaan maken",
- RedoingStatus: "Redoing",
- CheckingOutStatus: "Uitchecken",
- CommitFiles: "Commit bestanden",
- ViewItemFiles: "Bekijk gecommite bestanden",
- CommitFilesTitle: "Commit bestanden",
- CheckoutCommitFileTooltip: "Bestand uitchecken",
- DiscardOldFileChangeTooltip: "Uitsluit deze commit zijn veranderingen aan dit bestand",
- DiscardFileChangesTitle: "Uitsluit bestand zijn veranderingen",
- DiscardFileChangesPrompt: "Weet je zeker dat je de wijzigingen van deze commit in dit bestand wilt weggooien? Als dit bestand is gecreëerd in deze commit dan zal dit bestand worden verwijdert",
- DisabledForGPG: "Onderdelen niet beschikbaar voor gebruikers die GPG gebruiken",
- CreateRepo: "Niet in een git repository. Creëer een nieuwe git repository? (y/n): ",
- AutoStashTitle: "Autostash?",
- AutoStashPrompt: "Je moet je veranderingen stashen en poppen om ze over te brengen. Dit automatisch doen? (enter/esc)",
- StashPrefix: "Auto-stashing veranderingen voor ",
- Discard: "Bekijk 'veranderingen ongedaan maken' opties",
- Cancel: "Annuleren",
- DiscardAllChanges: "Negeer alle wijzigingen",
- DiscardUnstagedChanges: "Negeer unstaged wijzigingen",
- DiscardAllChangesToAllFiles: "Verwijder werkende tree",
- DiscardAnyUnstagedChanges: "Gooi unstaged wijzigingen weg",
- DiscardUntrackedFiles: "Negeer niet-gevonden bestanden",
- ViewResetOptions: `Bekijk reset opties`,
- HardReset: "Harde reset",
- CreateFixupCommit: `Creëer fixup commit`,
- SquashAboveCommitsTooltip: `Squash bovenstaande commits`,
- CreateFixupCommitTooltip: `Creëer fixup commit`,
- ExecuteCustomCommand: "Voer aangepaste commando uit",
- CustomCommand: "Aangepaste commando:",
- CommitChangesWithoutHook: "Commit veranderingen zonder pre-commit hook",
- SkipHookPrefixNotConfigured: "Je hebt nog niet een commit bericht voorvoegsel ingesteld voor het overslaan van hooks. Set `git.skipHookPrefix = 'WIP'` in je config",
- ResetTo: `Reset naar`,
- PressEnterToReturn: "Press om terug te gaan naar lazygit",
- ViewStashOptions: "Bekijk stash opties",
- StashAllChanges: "Stash-bestanden",
- StashAllChangesKeepIndex: "Stash staged wijzigingen",
- StashOptions: "Stash opties",
- NotARepository: "Fout: moet in een git repository uitgevoerd worden",
- Jump: "Ga naar paneel",
- DiscardPatch: "Patch weg gooien",
- DiscardPatchConfirm: "Je kan alleen maar een patch bouwen van 1 commit. Huidige patch weggooien?",
- CantPatchWhileRebasingError: "Je kan geen patch bouwen of patch commando uitvoeren wanneer je in een merging of rebasing state zit",
- ToggleAddToPatch: "Toggle bestand inbegrepen in patch",
- ViewPatchOptions: "Bekijk aangepaste patch opties",
- PatchOptionsTitle: "Patch opties",
- NoPatchError: "Nog geen patch gecreëerd. Om een patch te bouwen gebruik 'space' op een commit bestand of 'enter' om een spesiefieke lijnen toe te voegen",
- EnterCommitFile: "Enter bestand om geselecteerde regels toe te voegen aan de patch",
- ExitCustomPatchBuilder: `Sluit lijn-bij-lijn modus`,
- EnterUpstream: `Enter upstream als ' '`,
- ReturnToRemotesList: `Ga terug naar remotes lijst`,
- NewRemote: `Voeg een nieuwe remote toe`,
- NewRemoteName: `Nieuwe remote name:`,
- NewRemoteUrl: `Nieuwe remote url:`,
- EditRemoteName: `Enter updated remote naam voor {{.remoteName}}:`,
- EditRemoteUrl: `Enter updated remote url voor {{.remoteName}}:`,
- RemoveRemote: `Verwijder remote`,
- RemoveRemotePrompt: "Weet je zeker dat je deze remote wilt verwijderen",
- DeleteRemoteBranch: "Verwijder remote branch",
- DeleteRemoteBranchMessage: "Weet je zeker dat je deze remote branch wilt verwijderen",
- SetUpstream: "Stel in als upstream van uitgecheckte branch",
- SetAsUpstreamTooltip: "Stel in als upstream van uitgecheckte branch",
- SetUpstreamTitle: "Stel in als upstream branch",
- SetUpstreamMessage: "Weet je zeker dat je de upstream branch van '{{.checkedOut}}' naar '{{.selected}}' wilt zetten",
- EditRemoteTooltip: "Wijzig remote",
- TagCommit: "Tag commit",
- TagNameTitle: "Tag naam:",
- PushTagTitle: "Remote om tag '{{.tagName}}' te pushen naar:",
- PushTag: "Push tag",
- NewTag: "Creëer tag",
- FetchRemoteTooltip: "Fetch remote",
- FetchingRemoteStatus: "Remote fetchen",
- CheckoutCommit: "Checkout commit",
- SureCheckoutThisCommit: "Weet je zeker dat je deze commit wil uitchecken?",
- GitFlowOptions: "Laat git-flow opties zien",
- NotAGitFlowBranch: "Dit lijkt geen git flow branch te zijn",
- NewGitFlowBranchPrompt: "Nieuwe '{{.branchType}}' naam:",
- IgnoreTracked: "Negeer tracked bestand",
- IgnoreTrackedPrompt: "Weet je zeker dat je een getracked bestand wil negeren?",
- ViewResetToUpstreamOptions: "Bekijk upstream reset opties",
- NextScreenMode: "Volgende scherm modus (normaal/half/groot)",
- PrevScreenMode: "Vorige scherm modus",
- StartSearch: "Start met zoeken",
- Panel: "Paneel",
- Keybindings: "Sneltoetsen",
- RenameBranch: "Hernoem branch",
- NewBranchNamePrompt: "Noem een nieuwe branch naam",
- RenameBranchWarning: "Deze branch volgt een remote. Deze actie zal alleen de locale branch name wijzigen niet de naam van de remote branch. Verder gaan?",
- OpenKeybindingsMenu: "Open menu",
- ResetCherryPick: "Reset cherry-picked (gekopieerde) commits selectie",
- NextTab: "Volgende tabblad",
- PrevTab: "Vorige tabblad",
- CantUndoWhileRebasing: "Kan niet ongedaan maken terwijl je aan het rebasen bent",
- CantRedoWhileRebasing: "Kan niet opnieuw doen (redo) terwijl je aan het rebasen bent",
- MustStashWarning: "Een patch in de index stoppen vereist stashen en onstashen van je wijzigingen. Als er iets verkeert gaat kan je je bestanden terug vinden in de stash. Verder gaan?",
- MustStashTitle: "Moet stashen",
- ConfirmationTitle: "Bevestigingspaneel",
- PrevPage: "Vorige pagina",
- NextPage: "Volgende pagina",
- GotoTop: "Scroll naar boven",
- GotoBottom: "Scroll naar beneden",
- FilteringBy: "Filteren bij",
- ResetInParentheses: "(reset)",
- OpenFilteringMenu: "Bekijk scoping opties",
- FilterBy: "Filter bij",
- ExitFilterMode: "Stop met filteren bij pad",
- FilterPathOption: "Vulin pad om op te filteren",
- EnterFileName: "Vulin path:",
- FilteringMenuTitle: "Filteren",
- MustExitFilterModeTitle: "Command niet beschikbaar",
- MustExitFilterModePrompt: "Command niet beschikbaar in filter modus. Sluit filter modus?",
- Diff: "Diff",
- EnterRefToDiff: "Vul in ref naar diff",
- EnterRefName: "Vul in ref:",
- ExitDiffMode: "Sluit diff mode",
- DiffingMenuTitle: "Diffen",
- SwapDiff: "Keer diff richting om",
- ViewDiffingOptions: "Open diff menu",
- ShowingGitDiff: "Laat output zien voor:",
- CopyCommitHashToClipboard: "Kopieer commit hash naar klembord",
- CopyCommitMessageToClipboard: "Kopieer commit bericht naar klembord",
- CopyBranchNameToClipboard: "Kopieer branch name naar klembord",
- CopyPathToClipboard: "Kopieer de bestandsnaam naar het klembord",
- CommitPrefixPatternError: "Fout in commitPrefix patroon",
- NoFilesStagedTitle: "Geen bestanden gestaged",
- NoFilesStagedPrompt: "Je hebt geen bestanden gestaged. Commit alle bestanden?",
- BranchNotFoundTitle: "Branch niet gevonden",
- BranchNotFoundPrompt: "Branch niet gevonden. Creëer een nieuwe branch genaamd",
- PullRequestURLCopiedToClipboard: "Pull-aanvraag-URL gekopieerd naar klembord",
- CommitMessageCopiedToClipboard: "Commit message gekopieerd naar klembord",
- CopiedToClipboard: "gekopieerd naar klembord",
- NavigationTitle: "Lijstpaneel navigatie",
- ViewCommits: "Bekijk commits",
- ToggleTreeView: "Toggle bestandsboom weergave",
- CreateNewBranchFromCommit: "Creëer nieuwe branch van commit",
- CopySubmoduleNameToClipboard: "Kopieer submodule naam naar klembord",
- EnterSubmoduleTooltip: "Enter submodule",
- NewSubmodule: "Voeg nieuwe submodule toe",
- InitSubmoduleTooltip: "Initialiseer submodule",
- ViewBulkSubmoduleOptions: "Bekijk bulk submodule opties",
- CreatePullRequestOptions: "Bekijk opties voor pull-aanvraag",
- ConfirmRevertCommit: "Weet u zeker dat u {{.selectedCommit}} ongedaan wilt maken?",
- }
-}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index caa920de1ff..296239ddecd 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -24,7 +24,11 @@ type TranslationSet struct {
MainTitle string
StagingTitle string
MergingTitle string
- MergeConfirmTitle string
+ SquashMergeUncommittedTitle string
+ SquashMergeCommittedTitle string
+ SquashMergeUncommitted string
+ SquashMergeCommitted string
+ RegularMergeTooltip string
NormalTitle string
LogTitle string
CommitSummary string
@@ -32,6 +36,7 @@ type TranslationSet struct {
CredentialsPassword string
CredentialsPassphrase string
CredentialsPIN string
+ CredentialsToken string
PassUnameWrong string
Commit string
CommitTooltip string
@@ -42,7 +47,6 @@ type TranslationSet struct {
CommitChangesWithEditor string
FindBaseCommitForFixup string
FindBaseCommitForFixupTooltip string
- NoDeletedLinesInDiff string
NoBaseCommitsFound string
MultipleBaseCommitsFoundStaged string
MultipleBaseCommitsFoundUnstaged string
@@ -114,6 +118,7 @@ type TranslationSet struct {
CheckoutByName string
CheckoutByNameTooltip string
RemoteBranchCheckoutTitle string
+ RemoteBranchCheckoutPrompt string
CheckoutTypeNewBranch string
CheckoutTypeNewBranchTooltip string
CheckoutTypeDetachedHead string
@@ -133,6 +138,7 @@ type TranslationSet struct {
SureFixupThisCommit string
SureSquashThisCommit string
Squash string
+ SquashMerge string
PickCommitTooltip string
Pick string
CantPickDisabledReason string
@@ -165,6 +171,7 @@ type TranslationSet struct {
RewordCommitEditor string
NoCommitsThisBranch string
UpdateRefHere string
+ ExecCommandHere string
Error string
Undo string
UndoReflog string
@@ -174,6 +181,7 @@ type TranslationSet struct {
UndoMergeResolveTooltip string
DiscardAllTooltip string
DiscardUnstagedTooltip string
+ DiscardUnstagedDisabled string
Pop string
StashPopTooltip string
Drop string
@@ -198,6 +206,7 @@ type TranslationSet struct {
ForcePushPrompt string
ForcePushDisabled string
UpdatesRejected string
+ UpdatesRejectedAndForcePushDisabled string
CheckForUpdate string
CheckingForUpdates string
UpdateAvailableTitle string
@@ -226,6 +235,7 @@ type TranslationSet struct {
ExcludeFile string
RefreshFiles string
Merge string
+ RegularMerge string
MergeBranchTooltip string
ConfirmQuit string
SwitchRepo string
@@ -246,7 +256,6 @@ type TranslationSet struct {
ToggleSelectHunk string
ToggleSelectHunkTooltip string
ToggleSelectionForPatch string
- ToggleSelectionForPatchTooltip string
EditHunk string
EditHunkTooltip string
ToggleStagingView string
@@ -289,9 +298,10 @@ type TranslationSet struct {
RebasingFromBaseCommitTitle string
SimpleRebase string
InteractiveRebase string
+ RebaseOntoBaseBranch string
InteractiveRebaseTooltip string
+ RebaseOntoBaseBranchTooltip string
MustSelectTodoCommits string
- ConfirmMerge string
FwdNoUpstream string
FwdNoLocalUpstream string
FwdCommitsToPush string
@@ -304,7 +314,6 @@ type TranslationSet struct {
ChangingThisActionIsNotAllowed string
CherryPickCopy string
CherryPickCopyTooltip string
- CherryPickCopyRange string
CherryPickCopyRangeTooltip string
PasteCommits string
SureCherryPick string
@@ -468,6 +477,8 @@ type TranslationSet struct {
SetUpstream string
UnsetUpstream string
ViewDivergenceFromUpstream string
+ ViewDivergenceFromBaseBranch string
+ CouldNotDetermineBaseBranch string
DivergenceSectionHeaderLocal string
DivergenceSectionHeaderRemote string
ViewUpstreamResetOptions string
@@ -550,7 +561,6 @@ type TranslationSet struct {
OpenFilteringMenuTooltip string
FilterBy string
ExitFilterMode string
- ExitFilterModeAuthor string
FilterPathOption string
FilterAuthorOption string
EnterFileName string
@@ -575,6 +585,8 @@ type TranslationSet struct {
CommitHash string
CommitURL string
CopyCommitMessageToClipboard string
+ PasteCommitMessageFromClipboard string
+ SurePasteCommitMessage string
CommitMessage string
CommitSubject string
CommitAuthor string
@@ -630,169 +642,172 @@ type TranslationSet struct {
NavigationTitle string
SuggestionsCheatsheetTitle string
// Unlike the cheatsheet title above, the real suggestions title has a little message saying press tab to focus
- SuggestionsTitle string
- SuggestionsSubtitle string
- ExtrasTitle string
- PushingTagStatus string
- PullRequestURLCopiedToClipboard string
- CommitDiffCopiedToClipboard string
- CommitURLCopiedToClipboard string
- CommitMessageCopiedToClipboard string
- CommitSubjectCopiedToClipboard string
- CommitAuthorCopiedToClipboard string
- PatchCopiedToClipboard string
- CopiedToClipboard string
- ErrCannotEditDirectory string
- ErrStageDirWithInlineMergeConflicts string
- ErrRepositoryMovedOrDeleted string
- ErrWorktreeMovedOrRemoved string
- CommandLog string
- ToggleShowCommandLog string
- FocusCommandLog string
- CommandLogHeader string
- RandomTip string
- SelectParentCommitForMerge string
- ToggleWhitespaceInDiffView string
- ToggleWhitespaceInDiffViewTooltip string
- IgnoreWhitespaceDiffViewSubTitle string
- IgnoreWhitespaceNotSupportedHere string
- IncreaseContextInDiffView string
- IncreaseContextInDiffViewTooltip string
- DecreaseContextInDiffView string
- DecreaseContextInDiffViewTooltip string
- DiffContextSizeChanged string
- CreatePullRequestOptions string
- DefaultBranch string
- SelectBranch string
- CreatePullRequest string
- SelectConfigFile string
- NoConfigFileFoundErr string
- LoadingFileSuggestions string
- LoadingCommits string
- MustSpecifyOriginError string
- GitOutput string
- GitCommandFailed string
- AbortTitle string
- AbortPrompt string
- OpenLogMenu string
- OpenLogMenuTooltip string
- LogMenuTitle string
- ToggleShowGitGraphAll string
- ShowGitGraph string
- SortOrder string
- SortAlphabetical string
- SortByDate string
- SortByRecency string
- SortBasedOnReflog string
- SortCommits string
- CantChangeContextSizeError string
- OpenCommitInBrowser string
- ViewBisectOptions string
- ConfirmRevertCommit string
- RewordInEditorTitle string
- RewordInEditorPrompt string
- CheckoutPrompt string
- HardResetAutostashPrompt string
- UpstreamGone string
- NukeDescription string
- DiscardStagedChangesDescription string
- EmptyOutput string
- Patch string
- CustomPatch string
- CommitsCopied string
- CommitCopied string
- ResetPatch string
- ResetPatchTooltip string
- ApplyPatch string
- ApplyPatchTooltip string
- ApplyPatchInReverse string
- ApplyPatchInReverseTooltip string
- RemovePatchFromOriginalCommit string
- RemovePatchFromOriginalCommitTooltip string
- MovePatchOutIntoIndex string
- MovePatchOutIntoIndexTooltip string
- MovePatchIntoNewCommit string
- MovePatchIntoNewCommitTooltip string
- MovePatchToSelectedCommit string
- MovePatchToSelectedCommitTooltip string
- CopyPatchToClipboard string
- NoMatchesFor string
- MatchesFor string
- SearchKeybindings string
- SearchPrefix string
- FilterPrefix string
- ExitSearchMode string
- ExitTextFilterMode string
- Switch string
- SwitchToWorktree string
- SwitchToWorktreeTooltip string
- AlreadyCheckedOutByWorktree string
- BranchCheckedOutByWorktree string
- DetachWorktreeTooltip string
- Switching string
- RemoveWorktree string
- RemoveWorktreeTitle string
- DetachWorktree string
- DetachingWorktree string
- WorktreesTitle string
- WorktreeTitle string
- RemoveWorktreePrompt string
- ForceRemoveWorktreePrompt string
- RemovingWorktree string
- AddingWorktree string
- CantDeleteCurrentWorktree string
- AlreadyInWorktree string
- CantDeleteMainWorktree string
- NoWorktreesThisRepo string
- MissingWorktree string
- MainWorktree string
- NewWorktree string
- NewWorktreePath string
- NewWorktreeBase string
- RemoveWorktreeTooltip string
- BranchNameCannotBeBlank string
- NewBranchName string
- NewBranchNameLeaveBlank string
- ViewWorktreeOptions string
- CreateWorktreeFrom string
- CreateWorktreeFromDetached string
- LcWorktree string
- ChangingDirectoryTo string
- Name string
- Branch string
- Path string
- MarkedBaseCommitStatus string
- MarkAsBaseCommit string
- MarkAsBaseCommitTooltip string
- MarkedCommitMarker string
- PleaseGoToURL string
- NoCopiedCommits string
- DisabledMenuItemPrefix string
- QuickStartInteractiveRebase string
- QuickStartInteractiveRebaseTooltip string
- CannotQuickStartInteractiveRebase string
- ToggleRangeSelect string
- RangeSelectUp string
- RangeSelectDown string
- RangeSelectNotSupported string
- NoItemSelected string
- SelectedItemIsNotABranch string
- SelectedItemDoesNotHaveFiles string
- RangeSelectNotSupportedForSubmodules string
- OldCherryPickKeyWarning string
- CommandDoesNotSupportOpeningInEditor string
- Actions Actions
- Bisect Bisect
- Log Log
- BreakingChangesTitle string
- BreakingChangesMessage string
- BreakingChangesByVersion map[string]string
+ SuggestionsTitle string
+ SuggestionsSubtitle string
+ ExtrasTitle string
+ PushingTagStatus string
+ PullRequestURLCopiedToClipboard string
+ CommitDiffCopiedToClipboard string
+ CommitURLCopiedToClipboard string
+ CommitMessageCopiedToClipboard string
+ CommitSubjectCopiedToClipboard string
+ CommitAuthorCopiedToClipboard string
+ PatchCopiedToClipboard string
+ CopiedToClipboard string
+ ErrCannotEditDirectory string
+ ErrStageDirWithInlineMergeConflicts string
+ ErrRepositoryMovedOrDeleted string
+ ErrWorktreeMovedOrRemoved string
+ CommandLog string
+ ToggleShowCommandLog string
+ FocusCommandLog string
+ CommandLogHeader string
+ RandomTip string
+ SelectParentCommitForMerge string
+ ToggleWhitespaceInDiffView string
+ ToggleWhitespaceInDiffViewTooltip string
+ IgnoreWhitespaceDiffViewSubTitle string
+ IgnoreWhitespaceNotSupportedHere string
+ IncreaseContextInDiffView string
+ IncreaseContextInDiffViewTooltip string
+ DecreaseContextInDiffView string
+ DecreaseContextInDiffViewTooltip string
+ DiffContextSizeChanged string
+ IncreaseRenameSimilarityThreshold string
+ IncreaseRenameSimilarityThresholdTooltip string
+ DecreaseRenameSimilarityThreshold string
+ DecreaseRenameSimilarityThresholdTooltip string
+ RenameSimilarityThresholdChanged string
+ CreatePullRequestOptions string
+ DefaultBranch string
+ SelectBranch string
+ CreatePullRequest string
+ SelectConfigFile string
+ NoConfigFileFoundErr string
+ LoadingFileSuggestions string
+ LoadingCommits string
+ MustSpecifyOriginError string
+ GitOutput string
+ GitCommandFailed string
+ AbortTitle string
+ AbortPrompt string
+ OpenLogMenu string
+ OpenLogMenuTooltip string
+ LogMenuTitle string
+ ToggleShowGitGraphAll string
+ ShowGitGraph string
+ SortOrder string
+ SortAlphabetical string
+ SortByDate string
+ SortByRecency string
+ SortBasedOnReflog string
+ SortCommits string
+ CantChangeContextSizeError string
+ OpenCommitInBrowser string
+ ViewBisectOptions string
+ ConfirmRevertCommit string
+ RewordInEditorTitle string
+ RewordInEditorPrompt string
+ CheckoutPrompt string
+ HardResetAutostashPrompt string
+ UpstreamGone string
+ NukeDescription string
+ DiscardStagedChangesDescription string
+ EmptyOutput string
+ Patch string
+ CustomPatch string
+ CommitsCopied string
+ CommitCopied string
+ ResetPatch string
+ ResetPatchTooltip string
+ ApplyPatch string
+ ApplyPatchTooltip string
+ ApplyPatchInReverse string
+ ApplyPatchInReverseTooltip string
+ RemovePatchFromOriginalCommit string
+ RemovePatchFromOriginalCommitTooltip string
+ MovePatchOutIntoIndex string
+ MovePatchOutIntoIndexTooltip string
+ MovePatchIntoNewCommit string
+ MovePatchIntoNewCommitTooltip string
+ MovePatchToSelectedCommit string
+ MovePatchToSelectedCommitTooltip string
+ CopyPatchToClipboard string
+ NoMatchesFor string
+ MatchesFor string
+ SearchKeybindings string
+ SearchPrefix string
+ FilterPrefix string
+ ExitSearchMode string
+ ExitTextFilterMode string
+ Switch string
+ SwitchToWorktree string
+ SwitchToWorktreeTooltip string
+ AlreadyCheckedOutByWorktree string
+ BranchCheckedOutByWorktree string
+ DetachWorktreeTooltip string
+ Switching string
+ RemoveWorktree string
+ RemoveWorktreeTitle string
+ DetachWorktree string
+ DetachingWorktree string
+ WorktreesTitle string
+ WorktreeTitle string
+ RemoveWorktreePrompt string
+ ForceRemoveWorktreePrompt string
+ RemovingWorktree string
+ AddingWorktree string
+ CantDeleteCurrentWorktree string
+ AlreadyInWorktree string
+ CantDeleteMainWorktree string
+ NoWorktreesThisRepo string
+ MissingWorktree string
+ MainWorktree string
+ NewWorktree string
+ NewWorktreePath string
+ NewWorktreeBase string
+ RemoveWorktreeTooltip string
+ BranchNameCannotBeBlank string
+ NewBranchName string
+ NewBranchNameLeaveBlank string
+ ViewWorktreeOptions string
+ CreateWorktreeFrom string
+ CreateWorktreeFromDetached string
+ LcWorktree string
+ ChangingDirectoryTo string
+ Name string
+ Branch string
+ Path string
+ MarkedBaseCommitStatus string
+ MarkAsBaseCommit string
+ MarkAsBaseCommitTooltip string
+ MarkedCommitMarker string
+ PleaseGoToURL string
+ NoCopiedCommits string
+ DisabledMenuItemPrefix string
+ QuickStartInteractiveRebase string
+ QuickStartInteractiveRebaseTooltip string
+ CannotQuickStartInteractiveRebase string
+ ToggleRangeSelect string
+ RangeSelectUp string
+ RangeSelectDown string
+ RangeSelectNotSupported string
+ NoItemSelected string
+ SelectedItemIsNotABranch string
+ SelectedItemDoesNotHaveFiles string
+ RangeSelectNotSupportedForSubmodules string
+ OldCherryPickKeyWarning string
+ CommandDoesNotSupportOpeningInEditor string
+ Actions Actions
+ Bisect Bisect
+ Log Log
+ BreakingChangesTitle string
+ BreakingChangesMessage string
+ BreakingChangesByVersion map[string]string
}
type Bisect struct {
MarkStart string
- MarkSkipCurrent string
- MarkSkipSelected string
ResetTitle string
ResetPrompt string
ResetOption string
@@ -832,6 +847,7 @@ type Actions struct {
DeleteLocalBranch string
DeleteBranch string
Merge string
+ SquashMerge string
RebaseBranch string
RenameBranch string
CreateBranch string
@@ -880,7 +896,6 @@ type Actions struct {
Push string
Pull string
OpenFile string
- OpenFileTooltip string
StashAllChanges string
StashAllChangesKeepIndex string
StashStagedChanges string
@@ -971,8 +986,8 @@ for up-to-date information how to configure your editor.
`
// exporting this so we can use it in tests
-func EnglishTranslationSet() TranslationSet {
- return TranslationSet{
+func EnglishTranslationSet() *TranslationSet {
+ return &TranslationSet{
NotEnoughSpace: "Not enough space to render panels",
DiffTitle: "Diff",
FilesTitle: "Files",
@@ -984,7 +999,8 @@ func EnglishTranslationSet() TranslationSet {
UnstagedChanges: "Unstaged changes",
StagedChanges: "Staged changes",
MainTitle: "Main",
- MergeConfirmTitle: "Merge",
+ SquashMergeUncommittedTitle: "Squash merge and leave uncommitted",
+ SquashMergeCommittedTitle: "Squash merge and commit",
StagingTitle: "Main panel (staging)",
MergingTitle: "Main panel (merging)",
NormalTitle: "Main panel (normal)",
@@ -994,6 +1010,7 @@ func EnglishTranslationSet() TranslationSet {
CredentialsPassword: "Password",
CredentialsPassphrase: "Enter passphrase for SSH key",
CredentialsPIN: "Enter PIN for SSH key",
+ CredentialsToken: "Enter Token for SSH key",
PassUnameWrong: "Password, passphrase and/or username wrong",
Commit: "Commit",
CommitTooltip: "Commit staged changes.",
@@ -1004,7 +1021,6 @@ func EnglishTranslationSet() TranslationSet {
CommitChangesWithEditor: "Commit changes using git editor",
FindBaseCommitForFixup: "Find base commit for fixup",
FindBaseCommitForFixupTooltip: "Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: ",
- NoDeletedLinesInDiff: "No deleted lines in diff",
NoBaseCommitsFound: "No base commits found",
MultipleBaseCommitsFoundStaged: "Multiple base commits found. (Try staging fewer changes at once)",
MultipleBaseCommitsFoundUnstaged: "Multiple base commits found. (Try staging some of the changes)",
@@ -1075,6 +1091,7 @@ func EnglishTranslationSet() TranslationSet {
CheckoutByName: "Checkout by name",
CheckoutByNameTooltip: "Checkout by name. In the input box you can enter '-' to switch to the last branch.",
RemoteBranchCheckoutTitle: "Checkout {{.branchName}}",
+ RemoteBranchCheckoutPrompt: "How would you like to check out this branch?",
CheckoutTypeNewBranch: "New local branch",
CheckoutTypeNewBranchTooltip: "Checkout the remote branch as a local branch, tracking the remote branch.",
CheckoutTypeDetachedHead: "Detached head",
@@ -1090,11 +1107,13 @@ func EnglishTranslationSet() TranslationSet {
SquashTooltip: "Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it.",
NoCommitsThisBranch: "No commits for this branch",
UpdateRefHere: "Update branch '{{.ref}}' here",
+ ExecCommandHere: "Execute the following command here:",
CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
Fixup: "Fixup",
SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?",
SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?",
Squash: "Squash",
+ SquashMerge: "Squash Merge",
PickCommitTooltip: "Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase.",
Pick: "Pick",
CantPickDisabledReason: "Cannot pick a commit when not mid-rebase",
@@ -1136,6 +1155,7 @@ func EnglishTranslationSet() TranslationSet {
UndoMergeResolveTooltip: "Undo last merge conflict resolution.",
DiscardAllTooltip: "Discard both staged and unstaged changes in '{{.path}}'.",
DiscardUnstagedTooltip: "Discard unstaged changes in '{{.path}}'.",
+ DiscardUnstagedDisabled: "The selected items don't have both staged and unstaged changes.",
Pop: "Pop",
StashPopTooltip: "Apply the stash entry to your working directory and remove the stash entry.",
Drop: "Drop",
@@ -1160,6 +1180,7 @@ func EnglishTranslationSet() TranslationSet {
ForcePushPrompt: "Your branch has diverged from the remote branch. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to force push.",
ForcePushDisabled: "Your branch has diverged from the remote branch and you've disabled force pushing",
UpdatesRejected: "Updates were rejected. Please fetch and examine the remote changes before pushing again.",
+ UpdatesRejectedAndForcePushDisabled: "Updates were rejected and you have disabled force pushing",
CheckForUpdate: "Check for update",
CheckingForUpdates: "Checking for updates...",
UpdateAvailableTitle: "Update available!",
@@ -1188,10 +1209,11 @@ func EnglishTranslationSet() TranslationSet {
ExcludeFile: `Add to .git/info/exclude`,
RefreshFiles: `Refresh files`,
Merge: `Merge`,
- MergeBranchTooltip: "Merge selected branch into currently checked out branch.",
+ RegularMerge: "Regular merge",
+ MergeBranchTooltip: "View options for merging the selected item into the current branch (regular merge, squash merge)",
ConfirmQuit: `Are you sure you want to quit?`,
SwitchRepo: `Switch to a recent repo`,
- AllBranchesLogGraph: `Show all branch logs`,
+ AllBranchesLogGraph: `Show/cycle all branch logs`,
UnsupportedGitService: `Unsupported git service`,
CreatePullRequest: `Create pull request`,
CopyPullRequestURL: `Copy pull request URL to clipboard`,
@@ -1251,13 +1273,17 @@ func EnglishTranslationSet() TranslationSet {
KeybindingsMenuSectionLocal: "Local",
KeybindingsMenuSectionGlobal: "Global",
KeybindingsMenuSectionNavigation: "Navigation",
- RebasingTitle: "Rebase '{{.checkedOutBranch}}' onto '{{.ref}}'",
- RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base onto '{{.ref}}'",
- SimpleRebase: "Simple rebase",
- InteractiveRebase: "Interactive rebase",
+ RebasingTitle: "Rebase '{{.checkedOutBranch}}'",
+ RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base",
+ SimpleRebase: "Simple rebase onto '{{.ref}}'",
+ InteractiveRebase: "Interactive rebase onto '{{.ref}}'",
+ RebaseOntoBaseBranch: "Rebase onto base branch ({{.baseBranch}})",
InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing.",
+ RebaseOntoBaseBranchTooltip: "Rebase the checked out branch onto its base branch (i.e. the closest main branch).",
MustSelectTodoCommits: "When rebasing, this action only works on a selection of TODO commits.",
- ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?",
+ SquashMergeUncommitted: "Squash merge '{{.selectedBranch}}' into the working tree.",
+ SquashMergeCommitted: "Squash merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}' as a single commit.",
+ RegularMergeTooltip: "Merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'.",
FwdNoUpstream: "Cannot fast-forward a branch with no upstream",
FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally",
FwdCommitsToPush: "Cannot fast-forward a branch with commits to push",
@@ -1358,7 +1384,7 @@ func EnglishTranslationSet() TranslationSet {
ResetHardTooltip: "Reset HEAD to the chosen commit, and discard all changes between the current and chosen commit, as well as all current modifications in the working tree.",
ViewResetOptions: `Reset`,
FileResetOptionsTooltip: "View reset options for working tree (e.g. nuking the working tree).",
- FixupTooltip: "Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded.",
+ FixupTooltip: "Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded.",
CreateFixupCommit: "Create fixup commit",
CreateFixupCommitTooltip: "Create 'fixup!' commit for the selected commit. Later on, you can press `{{.squashAbove}}` on this same commit to apply all above fixup commits.",
CreateAmendCommit: `Create "amend!" commit`,
@@ -1434,12 +1460,14 @@ func EnglishTranslationSet() TranslationSet {
SetUpstream: "Set upstream of selected branch",
UnsetUpstream: "Unset upstream of selected branch",
ViewDivergenceFromUpstream: "View divergence from upstream",
+ ViewDivergenceFromBaseBranch: "View divergence from base branch ({{.baseBranch}})",
+ CouldNotDetermineBaseBranch: "Couldn't determine base branch",
DivergenceSectionHeaderLocal: "Local",
DivergenceSectionHeaderRemote: "Remote",
ViewUpstreamResetOptions: "Reset checked-out branch onto {{.upstream}}",
ViewUpstreamResetOptionsTooltip: "View options for resetting the checked-out branch onto {{upstream}}. Note: this will not reset the selected branch onto the upstream, it will reset the checked-out branch onto the upstream.",
ViewUpstreamRebaseOptions: "Rebase checked-out branch onto {{.upstream}}",
- ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebased the checked-out branch onto the upstream.",
+ ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebase the checked-out branch onto the upstream.",
UpstreamGenericName: "upstream of selected branch",
SetUpstreamTitle: "Set upstream branch",
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
@@ -1531,217 +1559,224 @@ func EnglishTranslationSet() TranslationSet {
ViewDiffingOptions: "View diffing options",
ViewDiffingOptionsTooltip: "View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction.",
// 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
- OpenCommandLogMenu: "View command log options",
- OpenCommandLogMenuTooltip: "View options for the command log e.g. show/hide the command log and focus the command log.",
- ShowingGitDiff: "Showing output for:",
- CommitDiff: "Commit diff",
- CopyCommitHashToClipboard: "Copy commit hash to clipboard",
- CommitHash: "Commit hash",
- CommitURL: "Commit URL",
- CopyCommitMessageToClipboard: "Copy commit message to clipboard",
- CommitMessage: "Commit message",
- CommitSubject: "Commit subject",
- CommitAuthor: "Commit author",
- CopyCommitAttributeToClipboard: "Copy commit attribute to clipboard",
- CopyCommitAttributeToClipboardTooltip: "Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author).",
- CopyBranchNameToClipboard: "Copy branch name to clipboard",
- CopyPathToClipboard: "Copy path to clipboard",
- CopySelectedTextToClipboard: "Copy selected text to clipboard",
- CommitPrefixPatternError: "Error in commitPrefix pattern",
- NoFilesStagedTitle: "No files staged",
- NoFilesStagedPrompt: "You have not staged any files. Commit all files?",
- BranchNotFoundTitle: "Branch not found",
- BranchNotFoundPrompt: "Branch not found. Create a new branch named",
- BranchUnknown: "Branch unknown",
- DiscardChangeTitle: "Discard change",
- DiscardChangePrompt: "Are you sure you want to discard this change (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipDiscardChangeWarning' to true",
- CreateNewBranchFromCommit: "Create new branch off of commit",
- BuildingPatch: "Building patch",
- ViewCommits: "View commits",
- MinGitVersionError: "Git version must be at least 2.20 (i.e. from 2018 onwards). Please upgrade your git version. Alternatively raise an issue at https://github.com/jesseduffield/lazygit/issues for lazygit to be more backwards compatible.",
- RunningCustomCommandStatus: "Running custom command",
- SubmoduleStashAndReset: "Stash uncommitted submodule changes and update",
- AndResetSubmodules: "And reset submodules",
- Enter: "Enter",
- EnterSubmoduleTooltip: "Enter submodule. After entering the submodule, you can press `{{.escape}}` to escape back to the parent repo.",
- CopySubmoduleNameToClipboard: "Copy submodule name to clipboard",
- RemoveSubmodule: "Remove submodule",
- RemoveSubmodulePrompt: "Are you sure you want to remove submodule '%s' and its corresponding directory? This is irreversible.",
- RemoveSubmoduleTooltip: "Remove the selected submodule and its corresponding directory.",
- ResettingSubmoduleStatus: "Resetting submodule",
- NewSubmoduleName: "New submodule name:",
- NewSubmoduleUrl: "New submodule URL:",
- NewSubmodulePath: "New submodule path:",
- NewSubmodule: "New submodule",
- AddingSubmoduleStatus: "Adding submodule",
- UpdateSubmoduleUrl: "Update URL for submodule '%s'",
- UpdatingSubmoduleUrlStatus: "Updating URL",
- EditSubmoduleUrl: "Update submodule URL",
- InitializingSubmoduleStatus: "Initializing submodule",
- InitSubmoduleTooltip: "Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule.",
- Update: "Update",
- Initialize: "Initialize",
- SubmoduleUpdateTooltip: "Update selected submodule.",
- UpdatingSubmoduleStatus: "Updating submodule",
- BulkInitSubmodules: "Bulk init submodules",
- BulkUpdateSubmodules: "Bulk update submodules",
- BulkDeinitSubmodules: "Bulk deinit submodules",
- ViewBulkSubmoduleOptions: "View bulk submodule options",
- BulkSubmoduleOptions: "Bulk submodule options",
- RunningCommand: "Running command",
- SubCommitsTitle: "Sub-commits",
- SubmodulesTitle: "Submodules",
- NavigationTitle: "List panel navigation",
- SuggestionsCheatsheetTitle: "Suggestions",
- SuggestionsTitle: "Suggestions (press %s to focus)",
- SuggestionsSubtitle: "(press %s to delete, %s to edit)",
- ExtrasTitle: "Command log",
- PushingTagStatus: "Pushing tag",
- PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
- CommitDiffCopiedToClipboard: "Commit diff copied to clipboard",
- CommitURLCopiedToClipboard: "Commit URL copied to clipboard",
- CommitMessageCopiedToClipboard: "Commit message copied to clipboard",
- CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard",
- CommitAuthorCopiedToClipboard: "Commit author copied to clipboard",
- PatchCopiedToClipboard: "Patch copied to clipboard",
- CopiedToClipboard: "copied to clipboard",
- ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files",
- ErrStageDirWithInlineMergeConflicts: "Cannot stage/unstage directory containing files with inline merge conflicts. Please fix up the merge conflicts first",
- ErrRepositoryMovedOrDeleted: "Cannot find repo. It might have been moved or deleted ¯\\_(ツ)_/¯",
- CommandLog: "Command log",
- ErrWorktreeMovedOrRemoved: "Cannot find worktree. It might have been moved or removed ¯\\_(ツ)_/¯",
- ToggleShowCommandLog: "Toggle show/hide command log",
- FocusCommandLog: "Focus command log",
- CommandLogHeader: "You can hide/focus this panel by pressing '%s'\n",
- RandomTip: "Random tip",
- SelectParentCommitForMerge: "Select parent commit for merge",
- ToggleWhitespaceInDiffView: "Toggle whitespace",
- ToggleWhitespaceInDiffViewTooltip: "Toggle whether or not whitespace changes are shown in the diff view.",
- IgnoreWhitespaceDiffViewSubTitle: "(ignoring whitespace)",
- IgnoreWhitespaceNotSupportedHere: "Ignoring whitespace is not supported in this view",
- IncreaseContextInDiffView: "Increase diff context size",
- IncreaseContextInDiffViewTooltip: "Increase the amount of the context shown around changes in the diff view.",
- DecreaseContextInDiffView: "Decrease diff context size",
- DecreaseContextInDiffViewTooltip: "Decrease the amount of the context shown around changes in the diff view.",
- DiffContextSizeChanged: "Changed diff context size to %d",
- CreatePullRequestOptions: "View create pull request options",
- DefaultBranch: "Default branch",
- SelectBranch: "Select branch",
- SelectConfigFile: "Select config file",
- NoConfigFileFoundErr: "No config file found",
- LoadingFileSuggestions: "Loading file suggestions",
- LoadingCommits: "Loading commits",
- MustSpecifyOriginError: "Must specify a remote if specifying a branch",
- GitOutput: "Git output:",
- GitCommandFailed: "Git command failed. Check command log for details (open with %s)",
- AbortTitle: "Abort %s",
- AbortPrompt: "Are you sure you want to abort the current %s?",
- OpenLogMenu: "View log options",
- OpenLogMenuTooltip: "View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph.",
- LogMenuTitle: "Commit Log Options",
- ToggleShowGitGraphAll: "Toggle show whole git graph (pass the `--all` flag to `git log`)",
- ShowGitGraph: "Show git graph",
- SortOrder: "Sort order",
- SortAlphabetical: "Alphabetical",
- SortByDate: "Date",
- SortByRecency: "Recency",
- SortBasedOnReflog: "(based on reflog)",
- SortCommits: "Commit sort order",
- CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!",
- OpenCommitInBrowser: "Open commit in browser",
- ViewBisectOptions: "View bisect options",
- ConfirmRevertCommit: "Are you sure you want to revert {{.selectedCommit}}?",
- RewordInEditorTitle: "Reword in editor",
- RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?",
- HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.",
- CheckoutPrompt: "Are you sure you want to checkout '%s'?",
- UpstreamGone: "(upstream gone)",
- NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
- DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",
- EmptyOutput: "",
- Patch: "Patch",
- CustomPatch: "Custom patch",
- CommitsCopied: "commits copied", // lowercase because it's used in a sentence
- CommitCopied: "commit copied", // lowercase because it's used in a sentence
- ResetPatch: "Reset patch",
- ResetPatchTooltip: "Clear the current patch.",
- ApplyPatch: "Apply patch",
- ApplyPatchTooltip: "Apply the current patch to the working tree.",
- ApplyPatchInReverse: "Apply patch in reverse",
- ApplyPatchInReverseTooltip: "Apply the current patch in reverse to the working tree.",
- RemovePatchFromOriginalCommit: "Remove patch from original commit (%s)",
- RemovePatchFromOriginalCommitTooltip: "Remove the current patch from its commit. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, and then continuing the rebase. If later commits depend on the patch, you may need to resolve conflicts.",
- MovePatchOutIntoIndex: "Move patch out into index",
- MovePatchOutIntoIndexTooltip: "Move the patch out of its commit and into the index. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, continuing the rebase to completion, and then applying the patch to the index. If later commits depend on the patch, you may need to resolve conflicts.",
- MovePatchIntoNewCommit: "Move patch into new commit",
- MovePatchIntoNewCommitTooltip: "Move the patch out of its commit and into a new commit sitting on top of the original commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then applying the patch to the index and committing it as a new commit, before continuing the rebase to completion. If later commits depend on the patch, you may need to resolve conflicts.",
- MovePatchToSelectedCommit: "Move patch to selected commit (%s)",
- MovePatchToSelectedCommitTooltip: "Move the patch out of its original commit and into the selected commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then continuing the rebase up to the selected commit, before applying the patch forward and amending the seleced commit. The rebase is then continued to completion. If commits between the source and destination commit depend on the patch, you may need to resolve conflicts.",
- CopyPatchToClipboard: "Copy patch to clipboard",
- NoMatchesFor: "No matches for '%s' %s",
- ExitSearchMode: "%s: Exit search mode",
- ExitTextFilterMode: "%s: Exit filter mode",
- MatchesFor: "matches for '%s' (%d of %d) %s", // lowercase because it's after other text
- SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
- SearchPrefix: "Search: ",
- FilterPrefix: "Filter: ",
- WorktreesTitle: "Worktrees",
- WorktreeTitle: "Worktree",
- Switch: "Switch",
- SwitchToWorktree: "Switch to worktree",
- SwitchToWorktreeTooltip: "Switch to the selected worktree.",
- AlreadyCheckedOutByWorktree: "This branch is checked out by worktree {{.worktreeName}}. Do you want to switch to that worktree?",
- BranchCheckedOutByWorktree: "Branch {{.branchName}} is checked out by worktree {{.worktreeName}}",
- DetachWorktreeTooltip: "This will run `git checkout --detach` on the worktree so that it stops hogging the branch, but the worktree's working tree will be left alone.",
- Switching: "Switching",
- RemoveWorktree: "Remove worktree",
- RemoveWorktreeTitle: "Remove worktree",
- RemoveWorktreePrompt: "Are you sure you want to remove worktree '{{.worktreeName}}'?",
- ForceRemoveWorktreePrompt: "'{{.worktreeName}}' contains modified or untracked files (to be honest, it could contain both). Are you sure you want to remove it?",
- RemovingWorktree: "Deleting worktree",
- DetachWorktree: "Detach worktree",
- DetachingWorktree: "Detaching worktree",
- AddingWorktree: "Adding worktree",
- CantDeleteCurrentWorktree: "You cannot remove the current worktree!",
- AlreadyInWorktree: "You are already in the selected worktree",
- CantDeleteMainWorktree: "You cannot remove the main worktree!",
- NoWorktreesThisRepo: "No worktrees",
- MissingWorktree: "(missing)",
- MainWorktree: "(main)",
- NewWorktree: "New worktree",
- NewWorktreePath: "New worktree path",
- NewWorktreeBase: "New worktree base ref",
- RemoveWorktreeTooltip: "Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory.",
- BranchNameCannotBeBlank: "Branch name cannot be blank",
- NewBranchName: "New branch name",
- NewBranchNameLeaveBlank: "New branch name (leave blank to checkout {{.default}})",
- ViewWorktreeOptions: "View worktree options",
- CreateWorktreeFrom: "Create worktree from {{.ref}}",
- CreateWorktreeFromDetached: "Create worktree from {{.ref}} (detached)",
- LcWorktree: "worktree",
- ChangingDirectoryTo: "Changing directory to {{.path}}",
- Name: "Name",
- Branch: "Branch",
- Path: "Path",
- MarkedBaseCommitStatus: "Marked a base commit for rebase",
- MarkAsBaseCommit: "Mark as base commit for rebase",
- MarkAsBaseCommitTooltip: "Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command.",
- MarkedCommitMarker: "↑↑↑ Will rebase from here ↑↑↑",
- PleaseGoToURL: "Please go to {{.url}}",
- DisabledMenuItemPrefix: "Disabled: ",
- NoCopiedCommits: "No copied commits",
- QuickStartInteractiveRebase: "Start interactive rebase",
- QuickStartInteractiveRebaseTooltip: "Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.\nIf you would instead like to start an interactive rebase from the selected commit, press `{{.editKey}}`.",
- CannotQuickStartInteractiveRebase: "Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `{{.editKey}}`.",
- RangeSelectUp: "Range select up",
- RangeSelectDown: "Range select down",
- RangeSelectNotSupported: "Action does not support range selection, please select a single item",
- NoItemSelected: "No item selected",
- SelectedItemIsNotABranch: "Selected item is not a branch",
- SelectedItemDoesNotHaveFiles: "Selected item does not have files to view",
- RangeSelectNotSupportedForSubmodules: "Range select not supported for submodules",
- OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
- CommandDoesNotSupportOpeningInEditor: "This command doesn't support switching to the editor",
+ OpenCommandLogMenu: "View command log options",
+ OpenCommandLogMenuTooltip: "View options for the command log e.g. show/hide the command log and focus the command log.",
+ ShowingGitDiff: "Showing output for:",
+ CommitDiff: "Commit diff",
+ CopyCommitHashToClipboard: "Copy commit hash to clipboard",
+ CommitHash: "Commit hash",
+ CommitURL: "Commit URL",
+ CopyCommitMessageToClipboard: "Copy commit message to clipboard",
+ PasteCommitMessageFromClipboard: "Paste commit message from clipboard",
+ SurePasteCommitMessage: "Pasting will overwrite the current commit message, continue?",
+ CommitMessage: "Commit message",
+ CommitSubject: "Commit subject",
+ CommitAuthor: "Commit author",
+ CopyCommitAttributeToClipboard: "Copy commit attribute to clipboard",
+ CopyCommitAttributeToClipboardTooltip: "Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author).",
+ CopyBranchNameToClipboard: "Copy branch name to clipboard",
+ CopyPathToClipboard: "Copy path to clipboard",
+ CopySelectedTextToClipboard: "Copy selected text to clipboard",
+ CommitPrefixPatternError: "Error in commitPrefix pattern",
+ NoFilesStagedTitle: "No files staged",
+ NoFilesStagedPrompt: "You have not staged any files. Commit all files?",
+ BranchNotFoundTitle: "Branch not found",
+ BranchNotFoundPrompt: "Branch not found. Create a new branch named",
+ BranchUnknown: "Branch unknown",
+ DiscardChangeTitle: "Discard change",
+ DiscardChangePrompt: "Are you sure you want to discard this change (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipDiscardChangeWarning' to true",
+ CreateNewBranchFromCommit: "Create new branch off of commit",
+ BuildingPatch: "Building patch",
+ ViewCommits: "View commits",
+ MinGitVersionError: "Git version must be at least 2.20 (i.e. from 2018 onwards). Please upgrade your git version. Alternatively raise an issue at https://github.com/jesseduffield/lazygit/issues for lazygit to be more backwards compatible.",
+ RunningCustomCommandStatus: "Running custom command",
+ SubmoduleStashAndReset: "Stash uncommitted submodule changes and update",
+ AndResetSubmodules: "And reset submodules",
+ Enter: "Enter",
+ EnterSubmoduleTooltip: "Enter submodule. After entering the submodule, you can press `{{.escape}}` to escape back to the parent repo.",
+ CopySubmoduleNameToClipboard: "Copy submodule name to clipboard",
+ RemoveSubmodule: "Remove submodule",
+ RemoveSubmodulePrompt: "Are you sure you want to remove submodule '%s' and its corresponding directory? This is irreversible.",
+ RemoveSubmoduleTooltip: "Remove the selected submodule and its corresponding directory.",
+ ResettingSubmoduleStatus: "Resetting submodule",
+ NewSubmoduleName: "New submodule name:",
+ NewSubmoduleUrl: "New submodule URL:",
+ NewSubmodulePath: "New submodule path:",
+ NewSubmodule: "New submodule",
+ AddingSubmoduleStatus: "Adding submodule",
+ UpdateSubmoduleUrl: "Update URL for submodule '%s'",
+ UpdatingSubmoduleUrlStatus: "Updating URL",
+ EditSubmoduleUrl: "Update submodule URL",
+ InitializingSubmoduleStatus: "Initializing submodule",
+ InitSubmoduleTooltip: "Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule.",
+ Update: "Update",
+ Initialize: "Initialize",
+ SubmoduleUpdateTooltip: "Update selected submodule.",
+ UpdatingSubmoduleStatus: "Updating submodule",
+ BulkInitSubmodules: "Bulk init submodules",
+ BulkUpdateSubmodules: "Bulk update submodules",
+ BulkDeinitSubmodules: "Bulk deinit submodules",
+ ViewBulkSubmoduleOptions: "View bulk submodule options",
+ BulkSubmoduleOptions: "Bulk submodule options",
+ RunningCommand: "Running command",
+ SubCommitsTitle: "Sub-commits",
+ SubmodulesTitle: "Submodules",
+ NavigationTitle: "List panel navigation",
+ SuggestionsCheatsheetTitle: "Suggestions",
+ SuggestionsTitle: "Suggestions (press %s to focus)",
+ SuggestionsSubtitle: "(press %s to delete, %s to edit)",
+ ExtrasTitle: "Command log",
+ PushingTagStatus: "Pushing tag",
+ PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
+ CommitDiffCopiedToClipboard: "Commit diff copied to clipboard",
+ CommitURLCopiedToClipboard: "Commit URL copied to clipboard",
+ CommitMessageCopiedToClipboard: "Commit message copied to clipboard",
+ CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard",
+ CommitAuthorCopiedToClipboard: "Commit author copied to clipboard",
+ PatchCopiedToClipboard: "Patch copied to clipboard",
+ CopiedToClipboard: "copied to clipboard",
+ ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files",
+ ErrStageDirWithInlineMergeConflicts: "Cannot stage/unstage directory containing files with inline merge conflicts. Please fix up the merge conflicts first",
+ ErrRepositoryMovedOrDeleted: "Cannot find repo. It might have been moved or deleted ¯\\_(ツ)_/¯",
+ CommandLog: "Command log",
+ ErrWorktreeMovedOrRemoved: "Cannot find worktree. It might have been moved or removed ¯\\_(ツ)_/¯",
+ ToggleShowCommandLog: "Toggle show/hide command log",
+ FocusCommandLog: "Focus command log",
+ CommandLogHeader: "You can hide/focus this panel by pressing '%s'\n",
+ RandomTip: "Random tip",
+ SelectParentCommitForMerge: "Select parent commit for merge",
+ ToggleWhitespaceInDiffView: "Toggle whitespace",
+ ToggleWhitespaceInDiffViewTooltip: "Toggle whether or not whitespace changes are shown in the diff view.",
+ IgnoreWhitespaceDiffViewSubTitle: "(ignoring whitespace)",
+ IgnoreWhitespaceNotSupportedHere: "Ignoring whitespace is not supported in this view",
+ IncreaseContextInDiffView: "Increase diff context size",
+ IncreaseContextInDiffViewTooltip: "Increase the amount of the context shown around changes in the diff view.",
+ DecreaseContextInDiffView: "Decrease diff context size",
+ DecreaseContextInDiffViewTooltip: "Decrease the amount of the context shown around changes in the diff view.",
+ DiffContextSizeChanged: "Changed diff context size to %d",
+ IncreaseRenameSimilarityThresholdTooltip: "Increase the similarity threshold for a deletion and addition pair to be treated as a rename.",
+ IncreaseRenameSimilarityThreshold: "Increase rename similarity threshold",
+ DecreaseRenameSimilarityThresholdTooltip: "Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.",
+ DecreaseRenameSimilarityThreshold: "Decrease rename similarity threshold",
+ RenameSimilarityThresholdChanged: "Changed rename similarity threshold to %d%%",
+ CreatePullRequestOptions: "View create pull request options",
+ DefaultBranch: "Default branch",
+ SelectBranch: "Select branch",
+ SelectConfigFile: "Select config file",
+ NoConfigFileFoundErr: "No config file found",
+ LoadingFileSuggestions: "Loading file suggestions",
+ LoadingCommits: "Loading commits",
+ MustSpecifyOriginError: "Must specify a remote if specifying a branch",
+ GitOutput: "Git output:",
+ GitCommandFailed: "Git command failed. Check command log for details (open with %s)",
+ AbortTitle: "Abort %s",
+ AbortPrompt: "Are you sure you want to abort the current %s?",
+ OpenLogMenu: "View log options",
+ OpenLogMenuTooltip: "View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph.",
+ LogMenuTitle: "Commit Log Options",
+ ToggleShowGitGraphAll: "Toggle show whole git graph (pass the `--all` flag to `git log`)",
+ ShowGitGraph: "Show git graph",
+ SortOrder: "Sort order",
+ SortAlphabetical: "Alphabetical",
+ SortByDate: "Date",
+ SortByRecency: "Recency",
+ SortBasedOnReflog: "(based on reflog)",
+ SortCommits: "Commit sort order",
+ CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!",
+ OpenCommitInBrowser: "Open commit in browser",
+ ViewBisectOptions: "View bisect options",
+ ConfirmRevertCommit: "Are you sure you want to revert {{.selectedCommit}}?",
+ RewordInEditorTitle: "Reword in editor",
+ RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?",
+ HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.",
+ CheckoutPrompt: "Are you sure you want to checkout '%s'?",
+ UpstreamGone: "(upstream gone)",
+ NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
+ DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",
+ EmptyOutput: "",
+ Patch: "Patch",
+ CustomPatch: "Custom patch",
+ CommitsCopied: "commits copied", // lowercase because it's used in a sentence
+ CommitCopied: "commit copied", // lowercase because it's used in a sentence
+ ResetPatch: "Reset patch",
+ ResetPatchTooltip: "Clear the current patch.",
+ ApplyPatch: "Apply patch",
+ ApplyPatchTooltip: "Apply the current patch to the working tree.",
+ ApplyPatchInReverse: "Apply patch in reverse",
+ ApplyPatchInReverseTooltip: "Apply the current patch in reverse to the working tree.",
+ RemovePatchFromOriginalCommit: "Remove patch from original commit (%s)",
+ RemovePatchFromOriginalCommitTooltip: "Remove the current patch from its commit. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, and then continuing the rebase. If later commits depend on the patch, you may need to resolve conflicts.",
+ MovePatchOutIntoIndex: "Move patch out into index",
+ MovePatchOutIntoIndexTooltip: "Move the patch out of its commit and into the index. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, continuing the rebase to completion, and then applying the patch to the index. If later commits depend on the patch, you may need to resolve conflicts.",
+ MovePatchIntoNewCommit: "Move patch into new commit",
+ MovePatchIntoNewCommitTooltip: "Move the patch out of its commit and into a new commit sitting on top of the original commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then applying the patch to the index and committing it as a new commit, before continuing the rebase to completion. If later commits depend on the patch, you may need to resolve conflicts.",
+ MovePatchToSelectedCommit: "Move patch to selected commit (%s)",
+ MovePatchToSelectedCommitTooltip: "Move the patch out of its original commit and into the selected commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then continuing the rebase up to the selected commit, before applying the patch forward and amending the seleced commit. The rebase is then continued to completion. If commits between the source and destination commit depend on the patch, you may need to resolve conflicts.",
+ CopyPatchToClipboard: "Copy patch to clipboard",
+ NoMatchesFor: "No matches for '%s' %s",
+ ExitSearchMode: "%s: Exit search mode",
+ ExitTextFilterMode: "%s: Exit filter mode",
+ MatchesFor: "matches for '%s' (%d of %d) %s", // lowercase because it's after other text
+ SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
+ SearchPrefix: "Search: ",
+ FilterPrefix: "Filter: ",
+ WorktreesTitle: "Worktrees",
+ WorktreeTitle: "Worktree",
+ Switch: "Switch",
+ SwitchToWorktree: "Switch to worktree",
+ SwitchToWorktreeTooltip: "Switch to the selected worktree.",
+ AlreadyCheckedOutByWorktree: "This branch is checked out by worktree {{.worktreeName}}. Do you want to switch to that worktree?",
+ BranchCheckedOutByWorktree: "Branch {{.branchName}} is checked out by worktree {{.worktreeName}}",
+ DetachWorktreeTooltip: "This will run `git checkout --detach` on the worktree so that it stops hogging the branch, but the worktree's working tree will be left alone.",
+ Switching: "Switching",
+ RemoveWorktree: "Remove worktree",
+ RemoveWorktreeTitle: "Remove worktree",
+ RemoveWorktreePrompt: "Are you sure you want to remove worktree '{{.worktreeName}}'?",
+ ForceRemoveWorktreePrompt: "'{{.worktreeName}}' contains modified or untracked files (to be honest, it could contain both). Are you sure you want to remove it?",
+ RemovingWorktree: "Deleting worktree",
+ DetachWorktree: "Detach worktree",
+ DetachingWorktree: "Detaching worktree",
+ AddingWorktree: "Adding worktree",
+ CantDeleteCurrentWorktree: "You cannot remove the current worktree!",
+ AlreadyInWorktree: "You are already in the selected worktree",
+ CantDeleteMainWorktree: "You cannot remove the main worktree!",
+ NoWorktreesThisRepo: "No worktrees",
+ MissingWorktree: "(missing)",
+ MainWorktree: "(main)",
+ NewWorktree: "New worktree",
+ NewWorktreePath: "New worktree path",
+ NewWorktreeBase: "New worktree base ref",
+ RemoveWorktreeTooltip: "Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory.",
+ BranchNameCannotBeBlank: "Branch name cannot be blank",
+ NewBranchName: "New branch name",
+ NewBranchNameLeaveBlank: "New branch name (leave blank to checkout {{.default}})",
+ ViewWorktreeOptions: "View worktree options",
+ CreateWorktreeFrom: "Create worktree from {{.ref}}",
+ CreateWorktreeFromDetached: "Create worktree from {{.ref}} (detached)",
+ LcWorktree: "worktree",
+ ChangingDirectoryTo: "Changing directory to {{.path}}",
+ Name: "Name",
+ Branch: "Branch",
+ Path: "Path",
+ MarkedBaseCommitStatus: "Marked a base commit for rebase",
+ MarkAsBaseCommit: "Mark as base commit for rebase",
+ MarkAsBaseCommitTooltip: "Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command.",
+ MarkedCommitMarker: "↑↑↑ Will rebase from here ↑↑↑",
+ PleaseGoToURL: "Please go to {{.url}}",
+ DisabledMenuItemPrefix: "Disabled: ",
+ NoCopiedCommits: "No copied commits",
+ QuickStartInteractiveRebase: "Start interactive rebase",
+ QuickStartInteractiveRebaseTooltip: "Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.\nIf you would instead like to start an interactive rebase from the selected commit, press `{{.editKey}}`.",
+ CannotQuickStartInteractiveRebase: "Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `{{.editKey}}`.",
+ RangeSelectUp: "Range select up",
+ RangeSelectDown: "Range select down",
+ RangeSelectNotSupported: "Action does not support range selection, please select a single item",
+ NoItemSelected: "No item selected",
+ SelectedItemIsNotABranch: "Selected item is not a branch",
+ SelectedItemDoesNotHaveFiles: "Selected item does not have files to view",
+ RangeSelectNotSupportedForSubmodules: "Range select not supported for submodules",
+ OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: