Compare commits

..

1 Commits

Author SHA1 Message Date
Stefan Haller
179f69e034 test 2024-04-26 10:03:22 +02:00
615 changed files with 13687 additions and 29304 deletions

View File

@@ -1,7 +0,0 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,go.sum,*.lock,.codespellrc,vendor,translations,Keybindings_*.md
check-hidden = true
# camel-cased
ignore-regex = (\b[A-Za-z][a-z]*[A-Z]\S+\b|\.edn\b|\S+…|\\nd\b)
ignore-words-list = fomrat,inbetween

3
.gitattributes vendored
View File

@@ -1,3 +0,0 @@
*.go text
*.md text eol=lf
*.json text eol=lf

View File

@@ -6,8 +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))
* [ ] If a new UserConfig entry was added, make sure it can be hot-reloaded (see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc
<!--

3
.github/release.yml vendored
View File

@@ -21,9 +21,6 @@ changelog:
- title: I18n 🌎
labels:
- i18n
- title: Performance Improvements 📊
labels:
- performance
- title: Other Changes
labels:
- "*"

View File

@@ -3,20 +3,20 @@ name: Continuous Delivery
on:
push:
tags:
- "v*"
- 'v*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Unshallow repo
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.22.x
go-version: 1.21.x
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v4
with:

View File

@@ -1,7 +1,7 @@
name: Continuous Integration
env:
GO_VERSION: 1.22
GO_VERSION: 1.21
on:
push:
@@ -28,18 +28,18 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.22.x
go-version: 1.21.x
- name: Test code
# we're passing -short so that we skip the integration tests, which will be run in parallel below
run: |
mkdir -p /tmp/code_coverage
go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -61,11 +61,11 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Restore Git cache
if: matrix.git-version != 'latest'
id: cache-git-restore
uses: actions/cache/restore@v4
uses: actions/cache/restore@v3
with:
path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}}
@@ -82,14 +82,14 @@ jobs:
run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install
- name: Save Git cache
if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
uses: actions/cache/save@v4
uses: actions/cache/save@v3
with:
path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}}
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.22.x
go-version: 1.21.x
- name: Print git version
run: git --version
- name: Test code
@@ -100,7 +100,7 @@ jobs:
mkdir -p /tmp/code_coverage
./scripts/run_integration_tests.sh
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -111,11 +111,11 @@ jobs:
GOARCH: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.22.x
go-version: 1.21.x
- name: Build linux binary
run: |
GOOS=linux go build
@@ -138,11 +138,11 @@ jobs:
GOARCH: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.22.x
go-version: 1.21.x
- name: Check Vendor Directory
# ensure our vendor directory matches up with our go modules
run: |
@@ -164,15 +164,15 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.22.x
go-version: 1.21.x
- name: Lint
uses: golangci/golangci-lint-action@v6.1.0
uses: golangci/golangci-lint-action@v3.7.0
with:
version: v1.60
version: latest
- name: errors
run: golangci-lint run
if: ${{ failure() }}
@@ -184,23 +184,17 @@ jobs:
with:
mode: exactly
count: 1
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance"
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
upload-coverage:
# List all jobs that produce coverage files
needs: [unit-tests, integration-tests]
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22.x
uses: actions/checkout@v3
- name: Download all coverage artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
path: /tmp/code_coverage
@@ -219,22 +213,3 @@ jobs:
CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} \
bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
--force-coverage-parser go -r coverage.out
check-for-fixups:
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/master'
steps:
# See https://github.com/actions/checkout/issues/552#issuecomment-1167086216
- name: "PR commits"
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
- name: "Checkout PR branch and all PR commits"
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: ${{ env.PR_FETCH_DEPTH }}
- name: Check for fixups
run: |
./scripts/check_for_fixups.sh ${{ github.event.pull_request.base.ref }}

View File

@@ -1,25 +0,0 @@
# Codespell configuration is within .codespellrc
---
name: Codespell
on:
push:
branches: [master]
pull_request:
branches: [master]
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@v1
- name: Codespell
uses: codespell-project/actions-codespell@v2

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1.2.2

2
.gitignore vendored
View File

@@ -5,7 +5,6 @@
# Hidden
.*
!.codespellrc
# Notes
*.notes
@@ -20,7 +19,6 @@ lazygit.exe
# Exceptions
!.gitignore
!.gitattributes
!.goreleaser.yml
!.golangci.yml
!.circleci/

View File

@@ -1,25 +1,23 @@
linters:
disable:
- structcheck # gives false positives
enable:
- gofumpt
- thelper
- goimports
- tparallel
- wastedassign
- exportloopref
- unparam
- prealloc
- unconvert
- exhaustive
- makezero
- nakedret
- copyloopvar
# - goconst # TODO: enable and fix issues
fast: false
linters-settings:
copyloopvar:
# Check all assigning the loop variable to another variable.
# Default: false
# If true, an assignment like `a := x` will be detected as an error.
check-alias: true
exhaustive:
default-signifies-exhaustive: true
staticcheck:
@@ -32,5 +30,5 @@ linters-settings:
max-func-lines: 0
run:
go: '1.22'
go: '1.21'
timeout: 10m

View File

@@ -154,7 +154,31 @@ If you want to trigger a debug session from VSCode, you can use the following sn
## Profiling
If you want to investigate what's contributing to CPU or memory usage, see [this separate document](docs/dev/Profiling.md).
If you want to investigate what's contributing to CPU usage you can add the following to the top of the `main()` function in `main.go`
```go
import "runtime/pprof"
func main() {
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
...
```
Then run lazygit, and afterwards, from your terminal, run:
```sh
go tool pprof --web cpu.prof
```
That should open an application which allows you to view the breakdown of CPU usage.
## Testing

View File

@@ -2,14 +2,14 @@
# docker build -t lazygit .
# docker run -it lazygit:latest /bin/sh
FROM golang:1.22 as build
FROM golang:1.21 as build
WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build
FROM alpine:3.19
FROM alpine:3.15
RUN apk add --no-cache -U git xdg-utils
WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY --from=build /go/src/github.com/jesseduffield/lazygit ./

View File

@@ -38,10 +38,6 @@ generate:
format:
gofumpt -l -w .
.PHONY: lint
lint:
golangci-lint run
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
.PHONY: integration-test-tui
integration-test-tui:

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +0,0 @@
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)
}
}

View File

@@ -1,6 +1,6 @@
# User Config
Default path for the global config file:
Default path for the config file:
- Linux: `~/.config/lazygit/config.yml`
- MacOS: `~/Library/Application\ Support/lazygit/config.yml`
@@ -16,8 +16,6 @@ If you want to change the config directory:
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
In addition to the global config file you can create repo-specific config files in `<repo>/.git/lazygit.yml`. Settings in these files override settings in the global config file. In addition, files called `.lazygit.yml` in any of the parent directories of a repo will also be loaded; this can be useful if you have settings that you want to apply to a group of repositories.
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
```yaml
@@ -31,602 +29,267 @@ to the top of your config file or via [Visual Studio Code settings.json config][
## Default
<!-- START CONFIG YAML: AUTOMATICALLY GENERATED with `go generate ./..., DO NOT UPDATE MANUALLY -->
```yaml
# Config relating to the Lazygit UI
gui:
# 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.
# 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
expandFocusedSidePanel: false
# 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
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'
theme:
# Border color of focused window
activeBorderColor:
- green
- bold
# Border color of non-focused windows
inactiveBorderColor:
- default
# Border color of focused window when searching in that window
- white
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
# 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
- blue # set to `default` to have no background colour
cherryPickedCommitBgColor:
- cyan
# Foreground color of marked base commit (for rebase)
markedBaseCommitFgColor:
cherryPickedCommitFgColor:
- 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
# 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
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
showRandomTip: true
# If true, show the command log
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
showCommandLog: true
# 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
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
commandLogSize: 8
# 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.
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
spinner:
# 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
# If true, jump to the Files panel after popping a stash
switchToFilesAfterStashPop: true
# If true, jump to the Files panel after applying a stash
switchToFilesAfterStashApply: true
# If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
switchTabsWithPanelJumpKeys: false
# Config relating to git
frames: ['|', '/', '-', '\\']
rate: 50 # spinner rate in milliseconds
statusPanelView: 'dashboard' # one of 'dashboard' | 'allBranchesLog'
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
# 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
autoWrapCommitMessage: true # automatic WYSIWYG wrapping of the commit message as you type
autoWrapWidth: 72 # if autoWrapCommitMessage is true, the width to wrap to
merging:
# If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
# Only applicable to unix users.
# only applicable to unix users
manualCommit: false
# 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
# If true, lazygit will automatically stage files that used to have merge
# conflicts but no longer do; and it will also ask you if you want to
# continue a merge or rebase if you've resolved all conflicts. If false, it
# won't do either of these things.
autoStageResolvedConflicts: 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
# extra args passed to `git merge`, e.g. --no-ff
args: ''
log:
# 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/
# 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
#
# Deprecated: Configure this with `Log menu -> Commit sort order` (<c-l> in the commits window by default).
order: topo-order
# This determines whether the git graph is rendered in the commits panel
# One of 'always' | 'never' | 'when-maximised'
order: 'topo-order'
# one of always, never, when-maximised
# this determines whether the git graph is rendered in the commits panel
#
# Deprecated: Configure this with `Log menu -> Show git graph` (<c-l> in the commits window by default).
showGraph: always
# displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
showGraph: 'always'
# displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
showWholeGraph: false
# When copying commit hashes to the clipboard, truncate them to this
# length. Set to 40 to disable truncation.
truncateCopiedCommitHashesTo: 12
# Periodic update checks
update:
# 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
# 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
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:
# 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.
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'.
update:
method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for
confirmOnQuit: false
# determines whether hitting 'esc' will quit the application when there is nothing to cancel/close
quitOnTopLevelReturn: false
disableStartupPopups: false
# 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
notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip' | 'quit'
promptToReturnFromSubprocess: true # display confirmation when subprocess terminates
keybinding:
universal:
quit: q
quit-alt1: <c-c>
return: <esc>
quitWithoutChangingDirectory: Q
togglePanel: <tab>
prevItem: <up>
nextItem: <down>
prevItem-alt: k
nextItem-alt: j
prevPage: ','
nextPage: .
scrollLeft: H
scrollRight: L
gotoTop: <
gotoBottom: '>'
toggleRangeSelect: v
rangeSelectDown: <s-down>
rangeSelectUp: <s-up>
prevBlock: <left>
nextBlock: <right>
prevBlock-alt: h
nextBlock-alt: l
nextBlock-alt2: <tab>
prevBlock-alt2: <backtab>
jumpToBlock:
- "1"
- "2"
- "3"
- "4"
- "5"
nextMatch: "n"
prevMatch: "N"
startSearch: /
optionMenu: <disabled>
optionMenu-alt1: '?'
select: <space>
goInto: <enter>
confirm: <enter>
confirmInEditor: <a-enter>
remove: d
new: "n"
edit: e
openFile: o
scrollUpMain: <pgup>
scrollDownMain: <pgdown>
scrollUpMain-alt1: K
scrollDownMain-alt1: J
scrollUpMain-alt2: <c-u>
scrollDownMain-alt2: <c-d>
executeShellCommand: ':'
createRebaseOptionsMenu: m
# 'Files' appended for legacy reasons
pushFiles: P
# 'Files' appended for legacy reasons
pullFiles: p
refresh: R
createPatchOptionsMenu: <c-p>
quit: 'q'
quit-alt1: '<c-c>' # alternative/alias of quit
return: '<esc>' # return to previous menu, will quit if there's nowhere to return
quitWithoutChangingDirectory: 'Q'
togglePanel: '<tab>' # goto the next panel
prevItem: '<up>' # go one line up
nextItem: '<down>' # 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: '<left>' # goto the previous block / panel
nextBlock: '<right>' # 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: <disabled> # show help menu
optionMenu-alt1: '?' # show help menu
select: '<space>'
goInto: '<enter>'
openRecentRepos: '<c-r>'
confirm: '<enter>'
remove: 'd'
new: 'n'
edit: 'e'
openFile: 'o'
scrollUpMain: '<pgup>' # main panel scroll up
scrollDownMain: '<pgdown>' # main panel scroll down
scrollUpMain-alt1: 'K' # main panel scroll up
scrollDownMain-alt1: 'J' # main panel scroll down
scrollUpMain-alt2: '<c-u>' # main panel scroll up
scrollDownMain-alt2: '<c-d>' # main panel scroll down
executeCustomCommand: ':'
createRebaseOptionsMenu: 'm'
pushFiles: 'P'
pullFiles: 'p'
refresh: 'R'
createPatchOptionsMenu: '<c-p>'
nextTab: ']'
prevTab: '['
nextScreenMode: +
prevScreenMode: _
undo: z
redo: <c-z>
filteringMenu: <c-s>
diffingMenu: W
diffingMenu-alt: <c-e>
copyToClipboard: <c-o>
openRecentRepos: <c-r>
submitEditorText: <enter>
nextScreenMode: '+'
prevScreenMode: '_'
undo: 'z'
redo: '<c-z>'
filteringMenu: '<c-s>'
diffingMenu: 'W'
diffingMenu-alt: '<c-e>' # deprecated
copyToClipboard: '<c-o>'
submitEditorText: '<enter>'
extrasMenu: '@'
toggleWhitespaceInDiffView: <c-w>
toggleWhitespaceInDiffView: '<c-w>'
increaseContextInDiffView: '}'
decreaseContextInDiffView: '{'
increaseRenameSimilarityThreshold: )
decreaseRenameSimilarityThreshold: (
openDiffTool: <c-t>
toggleRangeSelect: 'v'
rangeSelectUp: '<s-up>'
rangeSelectDown: '<s-down>'
status:
checkForUpdate: u
recentRepos: <enter>
allBranchesLogGraph: a
checkForUpdate: 'u'
recentRepos: '<enter>'
files:
commitChanges: c
commitChangesWithoutHook: w
amendLastCommit: A
commitChangesWithEditor: C
findBaseCommitForFixup: <c-f>
confirmDiscard: x
ignoreFile: i
refreshFiles: r
stashAllChanges: s
viewStashOptions: S
toggleStagedAll: a
viewResetOptions: D
fetch: f
commitChanges: 'c'
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
amendLastCommit: 'A'
commitChangesWithEditor: 'C'
findBaseCommitForFixup: '<c-f>'
confirmDiscard: 'x'
ignoreFile: 'i'
refreshFiles: 'r'
stashAllChanges: 's'
viewStashOptions: 'S'
toggleStagedAll: 'a' # stage/unstage all
viewResetOptions: 'D'
fetch: 'f'
toggleTreeView: '`'
openMergeTool: M
openStatusFilter: <c-b>
copyFileInfoToClipboard: "y"
openMergeTool: 'M'
openStatusFilter: '<c-b>'
branches:
createPullRequest: o
viewPullRequestOptions: O
copyPullRequestURL: <c-y>
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
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'
commits:
squashDown: s
renameCommit: r
renameCommitWithEditor: R
viewResetOptions: g
markCommitAsFixup: f
createFixupCommit: F
squashAboveCommits: S
moveDownCommit: <c-j>
moveUpCommit: <c-k>
amendToCommit: A
resetCommitAuthor: a
pickCommit: p
revertCommit: t
cherryPickCopy: C
pasteCommits: V
markCommitAsBaseForRebase: B
tagCommit: T
checkoutCommit: <space>
resetCherryPick: <c-R>
copyCommitAttributeToClipboard: "y"
openLogMenu: <c-l>
openInBrowser: o
viewBisectOptions: b
startInteractiveRebase: i
amendAttribute:
resetAuthor: a
setAuthor: A
addCoAuthor: c
squashDown: 's'
renameCommit: 'r'
renameCommitWithEditor: 'R'
viewResetOptions: 'g'
markCommitAsFixup: 'f'
createFixupCommit: 'F' # create fixup commit for this commit
squashAboveCommits: 'S'
moveDownCommit: '<c-j>' # move commit down one
moveUpCommit: '<c-k>' # move commit up one
amendToCommit: 'A'
amendAttributeMenu: 'a'
pickCommit: 'p' # pick commit (when mid-rebase)
revertCommit: 't'
cherryPickCopy: 'C'
pasteCommits: 'V'
tagCommit: 'T'
checkoutCommit: '<space>'
resetCherryPick: '<c-R>'
copyCommitMessageToClipboard: '<c-y>'
openLogMenu: '<c-l>'
viewBisectOptions: 'b'
stash:
popStash: g
renameStash: r
popStash: 'g'
renameStash: 'r'
commitFiles:
checkoutCommitFile: c
checkoutCommitFile: 'c'
main:
toggleSelectHunk: a
pickBothHunks: b
editSelectHunk: E
toggleSelectHunk: 'a'
pickBothHunks: 'b'
submodules:
init: i
update: u
bulkMenu: b
init: 'i'
update: 'u'
bulkMenu: 'b'
commitMessage:
commitMenu: <c-o>
commitMenu: '<c-o>'
amendAttribute:
addCoAuthor: 'c'
resetAuthor: 'a'
setAuthor: 'A'
```
<!-- END CONFIG YAML -->
## Platform Defaults
@@ -651,7 +314,7 @@ os:
open: 'open {{filename}}'
```
## Custom Command for Copying to and Pasting from Clipboard
## Custom Command for Copying to Clipboard
```yaml
os:
copyToClipboardCmd: ''
@@ -664,12 +327,6 @@ 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
@@ -682,7 +339,7 @@ os:
editPreset: 'vscode'
```
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, `xcode`, and `zed`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
@@ -891,15 +548,6 @@ Example:
- Branch name: feature/AB-123
- Commit message: [AB-123] Adding feature
```yaml
git:
commitPrefix:
pattern: "^\\w+\\/(\\w+-\\w+).*"
replace: '[$1] '
```
If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both `commitPrefixes` defined and an entry in `commitPrefixes` for the current repo, the `commitPrefixes` entry is given higher precedence. Repository folder names must be an exact match.
```yaml
git:
commitPrefixes:
@@ -908,21 +556,6 @@ 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:

View File

@@ -59,7 +59,6 @@ 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:
@@ -87,11 +86,6 @@ The permitted contexts are:
| stash | The 'Stash' tab |
| global | This keybinding will take affect everywhere |
> **Bonus**
>
> You can use a comma-separated string, such as `context: 'commits, subCommits'`, to make it effective in multiple contexts.
## Prompts
### Common fields
@@ -296,7 +290,9 @@ Here's an example using a command but not specifying anything else: so each line
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
```
SelectedCommit
SelectedLocalCommit
SelectedReflogCommit
SelectedSubCommit
SelectedFile
SelectedPath
SelectedLocalBranch
@@ -309,10 +305,7 @@ SelectedWorktree
CheckedOutBranch
```
(For legacy reasons, `SelectedLocalCommit`, `SelectedReflogCommit`, and `SelectedSubCommit` are also available, but they are deprecated.)
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file).
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit Lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Hash}}` and `{{.SelectedLocalBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model.
## Keybinding collisions

View File

@@ -26,8 +26,6 @@ git:
![](https://i.imgur.com/QJpQkF3.png)
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
## Diff-so-fancy
```yaml

View File

@@ -56,10 +56,22 @@ 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.
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.
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.

View File

@@ -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)

View File

@@ -13,6 +13,6 @@ includes interactive rebases, so for example amending a commit in the first
branch of the stack will "just work" in the sense that it keeps the other
branches properly stacked onto it.
Lazygit visualizes the individual branch heads in the stack by marking them with a
Lazygit visualizes the invidual branch heads in the stack by marking them with a
cyan asterisk (or a cyan branch symbol if you are using [nerd
fonts](Config.md#display-nerd-fonts-icons)).

View File

@@ -2,7 +2,7 @@
## Packages
* `pkg/app`: Contains startup code, initialises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
* `pkg/app`: Contains startup code, inititalises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
* `pkg/app/daemon`: Contains code relating to the lazygit daemon. This could be better named: it's is not a daemon in the sense that it's a long-running background process; rather it's a short-lived background process that we pass to git for certain tasks, like GIT_EDITOR for when we want to set the TODO file for an interactive rebase.
* `pkg/cheatsheet`: Generates the keybinding cheatsheets in `docs/keybindings`.
* `pkg/commands/git_commands`: All communication to the git binary happens here. So for example there's a `Checkout` method which calls `git checkout`.
@@ -12,7 +12,7 @@
* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc.
* `pkg/commands/patch`: Contains code for parsing and working with git patches
* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct).
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values. See [below](#using-userconfig) for some important information about using it.
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values.
* `pkg/constants`: Contains some constant strings (e.g. links to docs)
* `pkg/env`: Contains code relating to setting/getting environment variables
* `pkg/i18n`: Contains internationalised strings
@@ -86,12 +86,6 @@ The event loop is managed in the `MainLoop` function of `vendor/github.com/jesse
Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`.
## Using UserConfig
The UserConfig struct is loaded from lazygit's global config file (and possibly repo-specific config files). It can be re-loaded while lazygit is running, e.g. when the user edits one of the config files. In this case we should make sure that any new or changed config values take effect immediately. The easiest way to achieve this is what we do in most controllers or helpers: these have a pointer to the `common.Common` struct, which contains the UserConfig, and access it from there. Since the UserConfig instance in `common.Common` is updated whenever we reload the config, the code can be sure that it always uses an up-to-date value, and there's nothing else to do.
If that's not possible for some reason, see if you can add code to `Gui.onUserConfigLoaded` to update things from the new config; there are some examples in that function to use as a guide. If that's too hard to do too, add the config to the list in `Gui.checkForChangedConfigsThatDontAutoReload` so that the user is asked to quit and restart lazygit.
## Legacy code structure
Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file).

View File

@@ -1,229 +0,0 @@
# 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:
<details>
<summary>Click to show example</summary>
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, error
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.
</details>
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.

View File

@@ -1,69 +0,0 @@
# 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.

View File

@@ -4,5 +4,3 @@
* [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)

View File

@@ -14,11 +14,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-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 shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | 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. |
| `` <c-p> `` | View custom patch options | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
@@ -82,7 +80,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-r> `` | 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 squash, but the selected commit's message will be discarded. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, 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. |
@@ -164,14 +162,13 @@ 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 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | Merge | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -268,13 +265,12 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | Copy branch name to clipboard | |
| `` <space> `` | 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 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | Merge | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -311,7 +307,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 | |
| `` <enter> `` | Switch to a recent repo | |
| `` a `` | Show/cycle all branch logs | |
| `` a `` | Show all branch logs | |
## Sub-commits
@@ -353,7 +349,6 @@ 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |

View File

@@ -14,11 +14,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-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. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | カスタムコマンドを実行 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` <c-p> `` | View custom patch options | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | リフレッシュ | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
@@ -99,7 +97,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-r> `` | 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 squash, but the selected commit's message will be discarded. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, 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. |
@@ -185,7 +183,6 @@ 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -235,14 +232,13 @@ 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 `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | 現在のブランチにマージ | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -333,13 +329,12 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | ブランチ名をクリップボードにコピー | |
| `` <space> `` | チェックアウト | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 新しいブランチを作成 | |
| `` M `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | 現在のブランチにマージ | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |

View File

@@ -14,11 +14,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-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 shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | 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. |
| `` <c-p> `` | 커스텀 Patch 옵션 보기 | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
@@ -191,14 +189,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-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 `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | 현재 브랜치에 병합 | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -245,13 +242,12 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-o> `` | 브랜치명을 클립보드에 복사 | |
| `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 새 브랜치 생성 | |
| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | 현재 브랜치에 병합 | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -264,7 +260,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-r> `` | 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 squash, but the selected commit's message will be discarded. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, 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. |
@@ -326,7 +322,6 @@ 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |

View File

@@ -14,11 +14,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-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 shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | 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. |
| `` <c-p> `` | Bekijk aangepaste patch opties | |
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
@@ -103,14 +101,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-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 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | Merge in met huidige checked out branch | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -146,7 +143,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-r> `` | 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 squash, but the selected commit's message will be discarded. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to fixup, 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. |
@@ -246,13 +243,12 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | Kopieer branch name naar klembord | |
| `` <space> `` | 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 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | Merge in met huidige checked out branch | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -353,7 +349,6 @@ 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |

View File

@@ -14,11 +14,9 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-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. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | 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. |
| `` <c-p> `` | Wyświetl opcje niestandardowej łatki | |
| `` m `` | Pokaż opcje scalania/rebase | Pokaż opcje do przerwania/kontynuowania/pominięcia bieżącego scalania/rebase. |
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. |
@@ -136,7 +134,6 @@ 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. |
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
@@ -334,7 +331,6 @@ 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. |
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
@@ -363,7 +359,6 @@ 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. |
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |

View File

@@ -14,11 +14,9 @@ _Связки клавиш_
| `` @ `` | Открыть меню журнала команд | 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. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | Выполнить пользовательскую команду | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` <c-p> `` | Просмотреть пользовательские параметры патча | |
| `` m `` | Просмотреть параметры слияния/перебазирования | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
@@ -146,7 +144,7 @@ _Связки клавиш_
| `` <c-r> `` | Сбросить отобранную (скопированную | 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 squash, but the selected commit's message will be discarded. |
| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to fixup, 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. |
@@ -191,14 +189,13 @@ 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 `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | Слияние с текущей переключённой веткой | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -291,7 +288,6 @@ 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
@@ -303,13 +299,12 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | Скопировать название ветки в буфер обмена | |
| `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | Новая ветка | |
| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | Слияние с текущей переключённой веткой | Merge selected branch into currently checked out branch. |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |

View File

@@ -2,7 +2,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit 按键绑定
_图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## 全局键绑定
@@ -11,28 +11,26 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <c-r> `` | 切换到最近的仓库 | |
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游为配置,你可以在弹窗中配置上游分支。 |
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游为配置,你可以在弹窗中配置上游分支。 |
| `` ) `` | 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视图中围绕更改显示的上下文数量 |
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` @ `` | 打开命令日志菜单 | 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 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. |
| `` <c-p> `` | 查看自定义补丁选项 | |
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` m `` | 查看 合并/变基 选项 | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | 刷新 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | 下一屏模式正常/半屏/全屏 | |
| `` _ `` | 上一屏模式 | |
| `` ? `` | 打开菜单 | |
| `` <c-s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref然后反转比较方向。 |
| `` <c-e> `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref然后反转比较方向。 |
| `` <c-s> `` | 查看按路径过滤选项 | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | 退出 | |
| `` <esc> `` | 取消 | |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在diff视图中显示空白更改 |
| `` z `` | (通过 reflog)撤销「实验功能」 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改只考虑提交。 |
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改只考虑提交。 |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. |
| `` z `` | 通过 reflog撤销「实验功能」 | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | 通过 reflog重做「实验功能」 | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## 列表面板导航
@@ -43,8 +41,8 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` < `` | 滚动到顶部 | |
| `` > `` | 滚动到底部 | |
| `` v `` | 切换拖动选择 | |
| `` <s-down> `` | 向下扩展选择范围 | |
| `` <s-up> `` | 向上扩展选择范围 | |
| `` <s-down> `` | Range select down | |
| `` <s-up> `` | Range select up | |
| `` / `` | 开始搜索 | |
| `` H `` | 向左滚动 | |
| `` L `` | 向右滚动 | |
@@ -56,17 +54,27 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hashURLdiff、消息、作者)。 |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交拣选 | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重置已拣选复制的提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
## 分支页面
@@ -74,42 +82,41 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` i `` | 显示 git-flow 选项 | |
| `` <space> `` | 检出 | 检出选中的项目 |
| `` <space> `` | 检出 | Checkout selected item. |
| `` n `` | 新分支 | |
| `` o `` | 创建抓取请求 | |
| `` O `` | 创建抓取请求选项 | |
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | |
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` c `` | 按名称检出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
| `` 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. |
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
| `` f `` | 从上游快进此分支 | Fast-forward selected branch from its upstream. |
| `` T `` | 创建标签 | |
| `` s `` | 排序 | |
| `` s `` | Sort order | |
| `` g `` | 查看重置选项 | |
| `` R `` | 重命名分支 | |
| `` u `` | 查看上游选项 | 查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
| `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 子提交
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hashURLdiff、消息、作者)。 |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交拣选 | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重置已拣选复制的提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` w `` | View worktree options | |
| `` / `` | 开始搜索 | |
## 子模块
@@ -117,114 +124,104 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
| `` <enter> `` | 进入 | 输入子模块 |
| `` d `` | 删除 | 删除选定的子模块及其相应的目录 |
| `` u `` | 更新 | 更新子模块 |
| `` <enter> `` | Enter | 输入子模块 |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
| `` u `` | Update | 更新子模块 |
| `` n `` | 添加新的子模块 | |
| `` e `` | 更新子模块 URL | |
| `` i `` | 初始化 | 初始化子模块 |
| `` i `` | Initialize | 初始化子模块 |
| `` b `` | 查看批量子模块选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 工作区
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | 新建工作树 | |
| `` <space> `` | 切换 | 切换到选中的工作树 |
| `` o `` | 在编辑器中编写 | |
| `` d `` | 删除 | 删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。 |
| `` / `` | 通过文本过滤当前视图 | |
| `` / `` | Filter the current view by text | |
## 提交
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-r> `` | 重置已拣选复制的提交 | |
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
| `` f `` | 修正(fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
| `` r `` | 改写提交 | 重写所选提交的消息。 |
| `` 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. |
| `` r `` | 改写提交 | Reword the selected commit's message. |
| `` R `` | 使用编辑器重命名提交 | |
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
| `` e `` | 编辑(开始交互式变基) | 编辑提交 |
| `` i `` | 开始交互式变基 | 为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。
如果您想从所选提交启动交互式变基,请按 `e` |
| `` p `` | 拣选(Pick) | 选择提交(变基过程中) |
| `` 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. |
| `` e `` | Edit (start interactive rebase) | 编辑提交 |
| `` i `` | Start interactive rebase | 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.
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | 选择提交变基过程中 |
| `` F `` | 为此提交创建修正 | 创建修正提交 |
| `` S `` | 应用该修复提交 | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
| `` S `` | Apply fixup commits | 压缩在所选提交之上的所有“fixup!”提交自动压缩 |
| `` <c-j> `` | 下移提交 | |
| `` <c-k> `` | 上移提交 | |
| `` V `` | 粘贴提交(拣选) | |
| `` B `` | 标记一个主提交用于变基 | 选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。 |
| `` A `` | 修补(Amend) | 用已暂存的更来修补提交 |
| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 |
| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 |
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。 |
| `` <c-l> `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hashURLdiff、消息、作者)。 |
| `` V `` | 粘贴提交拣选 | |
| `` B `` | Mark as base commit for rebase | 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. |
| `` A `` | Amend | 用已暂存的更来修补提交 |
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` T `` | 标签提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
| `` <c-l> `` | 打开日志菜单 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交拣选 | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` w `` | View worktree options | |
| `` / `` | 开始搜索 | |
## 提交信息
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## 提交文件
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` c `` | 检出 | 检出文件 |
| `` d `` | 删除 | 放弃对此文件的提交更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <space> `` | 补丁中包含的切换文件 | 切换文件是否包含在自定义补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches |
| `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches |
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件则Enter进入该文件以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` d `` | Remove | 放弃对此文件的提交更 |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | 补丁中包含的切换文件 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 输入文件以将所选行添加到补丁中或切换目录折叠 | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` / `` | 开始搜索 | |
## 提交讯息
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## 文件
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` <space> `` | 切换暂存状态 | 为选定的文件切换暂存状态 |
| `` <c-b> `` | 通过状态过滤文件 | |
| `` y `` | 复制到剪贴板 | |
| `` c `` | 提交更 | 提交暂存文件 |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` <space> `` | 切换暂存状态 | Toggle staged for selected file. |
| `` <c-b> `` | Filter files by status | |
| `` y `` | Copy to clipboard | |
| `` c `` | 提交更 | Commit staged changes. |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` A `` | 修补最后一次提交 | |
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` C `` | 提交更改(使用编辑器编辑提交信息 | |
| `` <c-f> `` | Find base commit for fixup | 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: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | Edit | Open file in external editor. |
| `` o `` | 打开文件 | Open file in default application. |
| `` i `` | 忽略文件 | |
| `` r `` | 刷新文件 | |
| `` s `` | 贮藏 | 贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键 |
| `` S `` | 查看贮藏选项 | 查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更) |
| `` a `` | 切换所有文件的暂存状态 | 切换工作区中所有文件的已暂存/未暂存状态 |
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | 如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录 |
| `` d `` | 查看'放弃更'选项 | 查看选中文件的放弃变更选项 |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | 查看贮藏选项 | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | 切换所有文件的暂存状态 | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | 查看'放弃更'选项 | View options for discarding changes to the selected file. |
| `` g `` | 查看上游重置选项 | |
| `` D `` | 重置 | 查看工作树的重置选项(例如:清除工作树)。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` f `` | 抓取 | 从远程获取变更 |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 抓取 | Fetch changes from remote. |
| `` / `` | 开始搜索 | |
## 构建补丁中
@@ -234,10 +231,10 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <left> `` | 选择上一个区块 | |
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` a `` | 切换选择块 | Toggle hunk selection mode. |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` <space> `` | 添加/移除 行到补丁 | |
| `` <esc> `` | 退出逐行模式 | |
| `` / `` | 开始搜索 | |
@@ -246,15 +243,14 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 |
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
| `` P `` | 推送标签 | 推送选择的标签到远端。你将在弹窗中选择一个远端。 |
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <space> `` | 检出 | Checkout the selected tag tag as a detached HEAD. |
| `` n `` | 创建标签 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` 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. |
| `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 正在合并
@@ -266,10 +262,10 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <down> `` | 选择底部块 | |
| `` <left> `` | 选择上一个冲突 | |
| `` <right> `` | 选择下一个冲突 | |
| `` z `` | 撤销 | 撤消上次合并冲突解决 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` z `` | 撤销 | Undo last merge conflict resolution. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` o `` | 打开文件 | Open file in default application. |
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` <esc> `` | 返回文件面板 | |
## 正在暂存
@@ -279,19 +275,19 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <left> `` | 选择上一个区块 | |
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` a `` | 切换选择块 | Toggle hunk selection mode. |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时使用git reset丢弃该变更。当选择已暂存的变更时取消暂存该变更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` d `` | 取消变更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` <esc> `` | 返回文件面板 | |
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
| `` E `` | 编辑代码块 | 在外部编辑器中编辑选中的代码块 |
| `` c `` | 提交更 | 提交暂存文件 |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` <tab> `` | 切换到其他面板 | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | 提交更 | Commit staged changes. |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` C `` | 提交更改(使用编辑器编辑提交信息 | |
| `` <c-f> `` | Find base commit for fixup | 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: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 开始搜索 | |
## 正常
@@ -305,8 +301,8 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` o `` | 打开配置文件 | Open file in default application. |
| `` e `` | 编辑配置文件 | Open file in external editor. |
| `` u `` | 检查更新 | |
| `` <enter> `` | 切换到最近的仓库 | |
| `` a `` | 显示所有分支的日志 | |
@@ -324,46 +320,45 @@ _图例`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|-----|--------|-------------|
| `` <enter> `` | 执行 | |
| `` <esc> `` | 关闭 | |
| `` / `` | 通过文本过滤当前视图 | |
| `` / `` | Filter the current view by text | |
## 贮藏
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 应用 | 将贮藏项应用到您的工作目录。 |
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
| `` r `` | 重命名贮藏 | |
| `` <space> `` | 应用 | Apply the stash entry to your working directory. |
| `` g `` | 应用并删除 | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | 删除 | Remove the stash entry from the stash list. |
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` r `` | Rename stash | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 远程分支
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支或者将远程分支作分离的HEAD。 |
| `` <space> `` | 检出 | 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. |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 |
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
| `` s `` | 排序 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` 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. |
| `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 远程页面
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 查看分支 | |
| `` <enter> `` | View branches | |
| `` n `` | 添加新的远程仓库 | |
| `` d `` | 删除 | 删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。 |
| `` e `` | 编辑 | 编辑远程仓库 |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | Edit | 编辑远程仓库 |
| `` f `` | 抓取 | 抓取远程仓库 |
| `` / `` | 通过文本过滤当前视图 | |
| `` / `` | Filter the current view by text | |

View File

@@ -12,13 +12,11 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <pgup> (fn+up/shift+k) `` | 向上捲動主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下捲動主面板 | |
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 |
| `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 |
| `` ) `` | 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. |
| `` 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 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. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | 執行自訂命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` <c-p> `` | 檢視自訂補丁選項 | |
| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
@@ -60,8 +58,8 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` v `` | 切換拖曳選擇 | |
| `` a `` | 切換選擇程式碼塊 | Toggle hunk selection mode. |
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
| `` <esc> `` | 退出自訂補丁建立器 | |
| `` / `` | 搜尋 | |
@@ -84,9 +82,9 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <left> `` | 選擇上一個衝突 | |
| `` <right> `` | 選擇下一個衝突 | |
| `` z `` | 復原 | Undo last merge conflict resolution. |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool` |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` <esc> `` | 返回檔案面板 | |
## 主面板(預存)
@@ -100,12 +98,12 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` <esc> `` | 返回檔案面板 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. |
| `` c `` | 提交變更 | 提交暫存區變更 |
| `` c `` | 提交變更 | Commit staged changes. |
| `` w `` | 沒有預提交 hook 就提交更改 | |
| `` C `` | 使用 git 編輯器提交變更 | |
| `` <c-f> `` | Find base commit for fixup | 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: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
@@ -131,7 +129,7 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -156,7 +154,7 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | 在編輯器中開啟 | |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | 搜尋 | |
@@ -168,23 +166,23 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | |
| `` 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 squash, but the selected commit's message will be discarded. |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to fixup, 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. |
| `` e `` | 編輯(開始互動變基) | 編輯提交 |
| `` i `` | 開始互動變基 | 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.
| `` e `` | Edit (start interactive rebase) | 編輯提交 |
| `` i `` | Start interactive rebase | 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.
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | 挑選 | 挑選提交 (於變基過程中) |
| `` p `` | Pick | 挑選提交 (於變基過程中) |
| `` F `` | 建立修復提交 | 為此提交建立修復提交 |
| `` S `` | 壓縮上方所有「fixup」提交自動壓縮 | 是否壓縮上方 {{.commit}} 所有「fixup」提交 |
| `` <c-j> `` | 向下移動提交 | |
| `` <c-k> `` | 向上移動提交 | |
| `` V `` | 貼上提交 (揀選) | |
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 |
| `` A `` | 修改 | 使用已預存的更改修正提交 |
| `` A `` | Amend | 使用已預存的更改修正提交 |
| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. |
| `` t `` | 還原 | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
| `` <c-l> `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. |
@@ -193,7 +191,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` n `` | 從提交建立新分支 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -212,9 +210,9 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
| `` c `` | 檢出 | 檢出檔案 |
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
@@ -246,7 +244,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -257,23 +255,22 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <c-o> `` | 複製分支名稱到剪貼簿 | |
| `` i `` | 顯示 git-flow 選項 | |
| `` <space> `` | 檢出 | 檢出選定的項目。 |
| `` <space> `` | 檢出 | Checkout selected item. |
| `` n `` | 新分支 | |
| `` o `` | 建立拉取請求 | |
| `` O `` | 建立拉取請求選項 | |
| `` <c-y> `` | 複製拉取請求的 URL 到剪貼板 | |
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | 刪除 | View delete options for local/remote branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 |
| `` M `` | 合併到當前檢出的分支 | Merge selected branch into currently checked out branch. |
| `` f `` | 從上游快進此分支 | Fast-forward selected branch from its upstream. |
| `` T `` | 建立標籤 | |
| `` s `` | 排序規則 | |
| `` s `` | Sort order | |
| `` g `` | 檢視重設選項 | |
| `` R `` | 重新命名分支 | |
| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端 |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` u `` | 檢視上游設定 | 檢視有關上游分支的設定(例如重設至上游 |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -284,10 +281,9 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <space> `` | 檢出 | Checkout the selected tag tag as a detached HEAD. |
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` d `` | 刪除 | View delete options for local/remote tag. |
| `` 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 `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -299,35 +295,35 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
| `` <space> `` | 切換預存 | Toggle staged for selected file. |
| `` <c-b> `` | 篩選檔案 (預存/未預存) | |
| `` y `` | 複製到剪貼簿 | |
| `` c `` | 提交變更 | 提交暫存區變更 |
| `` y `` | Copy to clipboard | |
| `` c `` | 提交變更 | Commit staged changes. |
| `` w `` | 沒有預提交 hook 就提交更改 | |
| `` A `` | 修改上次提交 | |
| `` C `` | 使用 git 編輯器提交變更 | |
| `` <c-f> `` | Find base commit for fixup | 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: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | Edit | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` i `` | 忽略或排除檔案 | |
| `` r `` | 重新整理檔案 | |
| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 |
| `` g `` | 檢視遠端重設選項 | |
| `` D `` | 重設 | View reset options for working tree (e.g. nuking the working tree). |
| `` d `` | Discard | View options for discarding changes to the selected file. |
| `` g `` | 檢視上游重設選項 | |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool` |
| `` f `` | 擷取 | 同步遠端異動 |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 擷取 | Fetch changes from remote. |
| `` / `` | 搜尋 | |
## 狀態
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟設定檔案 | Open file in default application. |
| `` e `` | 編輯設定檔案 | Open file in external editor. |
| `` u `` | 檢查更新 | |
| `` <enter> `` | 切換到最近使用的版本庫 | |
| `` a `` | 顯示所有分支日誌 | |
@@ -346,7 +342,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <enter> `` | View branches | |
| `` n `` | 新增遠端 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | 編輯 | 編輯遠端 |
| `` e `` | Edit | 編輯遠端 |
| `` f `` | 擷取 | 擷取遠端 |
| `` / `` | 搜尋 | |
@@ -357,13 +353,12 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製分支名稱到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 新分支 | |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` M `` | 合併到當前檢出的分支 | Merge selected branch into currently checked out branch. |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | 刪除 | Delete the remote branch from the remote. |
| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 |
| `` s `` | 排序規則 | |
| `` 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. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |

0
file.txt Normal file
View File

14
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/jesseduffield/lazygit
go 1.22
go 1.21
require (
github.com/adrg/xdg v0.4.0
@@ -11,12 +11,11 @@ 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.20240928100326-393cf89a5d3f
github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513
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
@@ -24,7 +23,7 @@ require (
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
github.com/kyokomi/emoji/v2 v2.2.8
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.16
github.com/mattn/go-runewidth v0.0.15
github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0
github.com/sahilm/fuzzy v0.1.0
@@ -38,7 +37,6 @@ 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.8.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -75,8 +73,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.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.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
gopkg.in/warnings.v0 v0.1.2 // indirect
)

22
go.sum
View File

@@ -171,8 +171,6 @@ 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=
@@ -188,8 +186,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.20240928100326-393cf89a5d3f h1:ZzsAUDwPFLPITKLcJpMSqt/3rERdI8YRZKr2l0plrls=
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
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/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=
@@ -235,9 +233,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -424,8 +421,6 @@ 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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.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=
@@ -475,14 +470,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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.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/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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
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/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=
@@ -492,9 +487,8 @@ 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
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=

1
haha
View File

@@ -1 +0,0 @@
lolll

1
lol
View File

@@ -1 +0,0 @@
lol

View File

@@ -14,6 +14,7 @@ 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"
@@ -63,20 +64,22 @@ func Run(
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
userConfig := config.GetUserConfig()
appState := config.GetAppState()
log := newLogger(config)
// Initialize with English for the time being; the real translation set for
// the configured language will be read after reading the user config
tr := i18n.EnglishTranslationSet()
cmn := &common.Common{
Log: log,
Tr: tr,
AppState: appState,
Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
var err error
log := newLogger(config)
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language)
if err != nil {
return nil, err
}
cmn.SetUserConfig(userConfig)
return cmn, nil
return &common.Common{
Log: log,
Tr: tr,
UserConfig: userConfig,
AppState: appState,
Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
}, nil
}
func newLogger(cfg config.AppConfigurer) *logrus.Entry {
@@ -116,14 +119,7 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
return app, err
}
// If we're not in a repo, GetRepoPaths will return an error. 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 {
common.Log.Infof("Error getting repo paths: %v", err)
}
showRecentRepos, err := app.setupRepo(repoPaths)
showRecentRepos, err := app.setupRepo()
if err != nil {
return app, err
}
@@ -172,16 +168,14 @@ func openRecentRepo(app *App) bool {
return false
}
func (app *App) setupRepo(
repoPaths *git_commands.RepoPaths,
) (bool, error) {
func (app *App) setupRepo() (bool, error) {
if env.GetGitDirEnv() != "" {
// we've been given the git dir directly. Skip setup
// we've been given the git dir directly. We'll verify this dir when initializing our Git object
return false, nil
}
// if we are not in a git repo, we ask if we want to `git init`
if repoPaths == nil {
if err := commands.VerifyInGitRepo(app.OSCommand); err != nil {
cwd, err := os.Getwd()
if err != nil {
return false, err
@@ -193,7 +187,7 @@ func (app *App) setupRepo(
var shouldInitRepo bool
initialBranchArg := ""
switch app.UserConfig().NotARepository {
switch app.UserConfig.NotARepository {
case "prompt":
// Offer to initialize a new repository in current directory.
fmt.Print(app.Tr.CreateRepo)
@@ -227,7 +221,6 @@ func (app *App) setupRepo(
if err := app.OSCommand.Cmd.New(args).Run(); err != nil {
return false, err
}
return false, nil
}
@@ -245,7 +238,10 @@ func (app *App) setupRepo(
}
// Run this afterward so that the previous repo creation steps can run without this interfering
if repoPaths.IsBareRepo() {
if isBare, err := git_commands.IsBareRepo(app.OSCommand); isBare {
if err != nil {
return false, err
}
fmt.Print(app.Tr.BareRepo)

View File

@@ -246,18 +246,16 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
// Takes the hash of some commit, and the hash of a fixup commit that was created
// at the end of the branch, then moves the fixup commit down to right after the
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
// original commit, changing its type to "fixup"
type MoveFixupCommitDownInstruction struct {
OriginalHash string
FixupHash string
ChangeToFixup bool
OriginalHash string
FixupHash string
}
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string, changeToFixup bool) Instruction {
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string) Instruction {
return &MoveFixupCommitDownInstruction{
OriginalHash: originalHash,
FixupHash: fixupHash,
ChangeToFixup: changeToFixup,
OriginalHash: originalHash,
FixupHash: fixupHash,
}
}
@@ -271,7 +269,7 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar())
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, getCommentChar())
})
}

View File

@@ -4,8 +4,6 @@ import (
"bytes"
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"os"
"os/exec"
"path/filepath"
@@ -32,7 +30,6 @@ type cliArgs struct {
PrintVersionInfo bool
Debug bool
TailLogs bool
Profile bool
PrintDefaultConfig bool
PrintConfigDir bool
UseConfigDir string
@@ -136,12 +133,6 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
if integrationTest != nil {
integrationTest.SetupConfig(appConfig)
// Preserve the changes that the test setup just made to the config, so
// they don't get lost when we reload the config while running the test
// (which happens when switching between repos, going in and out of
// submodules, etc).
appConfig.SaveGlobalUserConfig()
}
common, err := NewCommon(appConfig)
@@ -154,14 +145,6 @@ 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))
@@ -188,9 +171,6 @@ 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")
@@ -222,7 +202,6 @@ func parseCliArgsAndEnvVars() *cliArgs {
PrintVersionInfo: printVersionInfo,
Debug: debug,
TailLogs: tailLogs,
Profile: profile,
PrintDefaultConfig: printDefaultConfig,
PrintConfigDir: printConfigDir,
UseConfigDir: useConfigDir,

View File

@@ -51,10 +51,7 @@ func GetKeybindingsDir() string {
}
func generateAtDir(cheatsheetDir string) {
translationSetsByLang, err := i18n.GetTranslationSets()
if err != nil {
log.Fatal(err)
}
translationSetsByLang := i18n.GetTranslationSets()
mConfig := config.NewDummyAppConfig()
for lang := range translationSetsByLang {
@@ -63,11 +60,6 @@ func generateAtDir(cheatsheetDir string) {
if err != nil {
log.Fatal(err)
}
tr, err := i18n.NewTranslationSetFromConfig(common.Log, lang)
if err != nil {
log.Fatal(err)
}
common.Tr = tr
mApp, _ := app.NewApp(mConfig, nil, common)
path := cheatsheetDir + "/Keybindings_" + lang + ".md"
file, err := os.Create(path)

View File

@@ -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)
})
}

View File

@@ -134,7 +134,7 @@ func NewGitCommandAux(
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
blameCommands := git_commands.NewBlameCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)

View File

@@ -38,10 +38,6 @@ func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewShell(cmdStr).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) NewInteractiveShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewInteractiveShell(cmdStr).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) Quote(str string) string {
return self.innerBuilder.Quote(str)
}

View File

@@ -4,16 +4,13 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"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 {
@@ -31,15 +28,6 @@ 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 {
@@ -146,7 +134,7 @@ func (self *BranchCommands) GetGraph(branchName string) (string, error) {
}
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd
templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName),
}
@@ -228,18 +216,13 @@ 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)...).
Arg(strings.Fields(self.UserConfig.Git.Merging.Args)...).
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.Squash, "--squash", "--ff").
Arg(branchName).
ToArgv()
@@ -247,40 +230,5 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
}
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
// 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()
}
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
branchesToCheckAgainst := []string{"HEAD"}
if branch.RemoteBranchStoredLocally() {
branchesToCheckAgainst = append(branchesToCheckAgainst, fmt.Sprintf("%s@{upstream}", branch.Name))
}
branchesToCheckAgainst = append(branchesToCheckAgainst, mainBranches.Get()...)
cmdArgs := NewGitCmd("rev-list").
Arg("--max-count=1").
Arg(branch.Name).
Arg(lo.Map(branchesToCheckAgainst, func(branch string, _ int) string {
return fmt.Sprintf("^%s", branch)
})...).
ToArgv()
stdout, _, err := self.cmd.New(cmdArgs).RunWithOutputs()
if err != nil {
return false, err
}
return stdout == "", nil
return self.cmd.New(str.ToArgv(self.UserConfig.Git.AllBranchesLogCmd)).DontLog()
}

View File

@@ -5,7 +5,6 @@ import (
"regexp"
"strconv"
"strings"
"time"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/go-git/v5/config"
@@ -15,7 +14,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
)
// context:
@@ -42,7 +40,6 @@ type BranchInfo struct {
// BranchLoader returns a list of Branch objects for the current repo
type BranchLoader struct {
*common.Common
*GitCommon
cmd oscommands.ICmdObjBuilder
getCurrentBranchInfo func() (BranchInfo, error)
config BranchLoaderConfigCommands
@@ -50,14 +47,12 @@ type BranchLoader struct {
func NewBranchLoader(
cmn *common.Common,
gitCommon *GitCommon,
cmd oscommands.ICmdObjBuilder,
getCurrentBranchInfo func() (BranchInfo, error),
config BranchLoaderConfigCommands,
) *BranchLoader {
return &BranchLoader{
Common: cmn,
GitCommon: gitCommon,
cmd: cmd,
getCurrentBranchInfo: getCurrentBranchInfo,
config: config,
@@ -65,14 +60,8 @@ func NewBranchLoader(
}
// Load the list of branches for the current repo
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))
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
branches := self.obtainBranches()
if self.AppState.LocalBranchSortOrder == "recency" {
reflogBranches := self.obtainReflogBranches(reflogCommits)
@@ -130,109 +119,12 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit,
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 "<ahead>\t<behind>"
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 {
func (self *BranchLoader) obtainBranches() []*models.Branch {
output, err := self.getRawBranches()
if err != nil {
panic(err)
@@ -255,7 +147,7 @@ func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch
}
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
return obtainBranch(split, storeCommitDateAsRecency, canUsePushTrack), true
return obtainBranch(split, storeCommitDateAsRecency), true
})
}
@@ -291,31 +183,23 @@ var branchFields = []string{
"refname:short",
"upstream:short",
"upstream:track",
"push:track",
"subject",
"objectname",
"committerdate:unix",
}
// Obtain branch information from parsed line output of getRawBranches()
func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack bool) *models.Branch {
func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch {
headMarker := split[0]
fullName := split[1]
upstreamName := split[2]
track := split[3]
pushTrack := split[4]
subject := split[5]
commitHash := split[6]
commitDate := split[7]
subject := split[4]
commitHash := split[5]
commitDate := split[6]
name := strings.TrimPrefix(fullName, "heads/")
aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track)
var aheadForPush, behindForPush string
if canUsePushTrack {
aheadForPush, behindForPush, _ = parseUpstreamInfo(upstreamName, pushTrack)
} else {
aheadForPush, behindForPush = aheadForPull, behindForPull
}
pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
recency := ""
if storeCommitDateAsRecency {
@@ -325,16 +209,14 @@ func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack
}
return &models.Branch{
Name: name,
Recency: recency,
AheadForPull: aheadForPull,
BehindForPull: behindForPull,
AheadForPush: aheadForPush,
BehindForPush: behindForPush,
UpstreamGone: gone,
Head: headMarker == "*",
Subject: subject,
CommitHash: commitHash,
Name: name,
Recency: recency,
Pushables: pushables,
Pullables: pullables,
UpstreamGone: gone,
Head: headMarker == "*",
Subject: subject,
CommitHash: commitHash,
}
}
@@ -350,10 +232,10 @@ func parseUpstreamInfo(upstreamName string, track string) (string, string, bool)
return "?", "?", true
}
ahead := parseDifference(track, `ahead (\d+)`)
behind := parseDifference(track, `behind (\d+)`)
pushables := parseDifference(track, `ahead (\d+)`)
pullables := parseDifference(track, `behind (\d+)`)
return ahead, behind, false
return pushables, pullables, false
}
func parseDifference(track string, regexStr string) string {

View File

@@ -25,101 +25,89 @@ func TestObtainBranch(t *testing.T) {
scenarios := []scenario{
{
testName: "TrimHeads",
input: []string{"", "heads/a_branch", "", "", "", "subject", "123", timeStamp},
input: []string{"", "heads/a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
AheadForPull: "?",
BehindForPull: "?",
AheadForPush: "?",
BehindForPush: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
Name: "a_branch",
Pushables: "?",
Pullables: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
},
},
{
testName: "NoUpstream",
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
AheadForPull: "?",
BehindForPull: "?",
AheadForPush: "?",
BehindForPush: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
Name: "a_branch",
Pushables: "?",
Pullables: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
},
},
{
testName: "IsHead",
input: []string{"*", "a_branch", "", "", "", "subject", "123", timeStamp},
input: []string{"*", "a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
AheadForPull: "?",
BehindForPull: "?",
AheadForPush: "?",
BehindForPush: "?",
Head: true,
Subject: "subject",
CommitHash: "123",
Name: "a_branch",
Pushables: "?",
Pullables: "?",
Head: true,
Subject: "subject",
CommitHash: "123",
},
},
{
testName: "IsBehindAndAhead",
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "subject", "123", timeStamp},
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
AheadForPull: "3",
BehindForPull: "2",
AheadForPush: "3",
BehindForPush: "2",
Head: false,
Subject: "subject",
CommitHash: "123",
Name: "a_branch",
Pushables: "3",
Pullables: "2",
Head: false,
Subject: "subject",
CommitHash: "123",
},
},
{
testName: "RemoteBranchIsGone",
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "subject", "123", timeStamp},
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
UpstreamGone: true,
AheadForPull: "?",
BehindForPull: "?",
AheadForPush: "?",
BehindForPush: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
Name: "a_branch",
UpstreamGone: true,
Pushables: "?",
Pullables: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
},
},
{
testName: "WithCommitDateAsRecency",
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: true,
expectedBranch: &models.Branch{
Name: "a_branch",
Recency: "2h",
AheadForPull: "?",
BehindForPull: "?",
AheadForPush: "?",
BehindForPush: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
Name: "a_branch",
Recency: "2h",
Pushables: "?",
Pullables: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
branch := obtainBranch(s.input, s.storeCommitDateAsRecency, true)
branch := obtainBranch(s.input, s.storeCommitDateAsRecency)
assert.EqualValues(t, s.expectedBranch, branch)
})
}

View File

@@ -41,6 +41,7 @@ func TestBranchGetCommitDifferences(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})
pushables, pullables := instance.GetCommitDifferences("HEAD", "@{u}")
@@ -88,6 +89,7 @@ func TestBranchDeleteBranch(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})
@@ -148,6 +150,7 @@ func TestBranchMerge(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expected, "", nil)
@@ -187,6 +190,7 @@ func TestBranchCheckout(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.Checkout("test", CheckoutOptions{Force: s.force}))
@@ -275,6 +279,7 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.CurrentBranchInfo())

View File

@@ -88,7 +88,7 @@ func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars
func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(summary, description)
skipHookPrefix := self.UserConfig().Git.SkipHookPrefix
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
cmdArgs := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify").
@@ -117,7 +117,7 @@ func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile s
}
// RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(summary string, description string) oscommands.ICmdObj {
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
messageArgs := self.commitMessageArgs(summary, description)
cmdArgs := NewGitCmd("commit").
@@ -125,7 +125,7 @@ func (self *CommitCommands) RewordLastCommit(summary string, description string)
Arg(messageArgs...).
ToArgv()
return self.cmd.New(cmdArgs)
return self.cmd.New(cmdArgs).Run()
}
func (self *CommitCommands) commitMessageArgs(summary string, description string) []string {
@@ -148,7 +148,7 @@ func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
}
func (self *CommitCommands) signoffFlag() string {
if self.UserConfig().Git.Commit.SignOff {
if self.UserConfig.Git.Commit.SignOff {
return "--signoff"
} else {
return ""
@@ -189,7 +189,7 @@ type Author struct {
func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) {
cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:%an%x00%ae", commitHash).
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitHash).
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
@@ -258,20 +258,19 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--color="+self.UserConfig().Git.Paging.ColorArg).
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat").
Arg("--decorate").
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()

View File

@@ -35,6 +35,11 @@ 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
}
@@ -51,6 +56,7 @@ func NewCommitLoader(
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
mainBranches: nil,
GitCommon: gitCommon,
}
}
@@ -66,7 +72,6 @@ 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
@@ -103,9 +108,9 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
go utils.Safe(func() {
defer wg.Done()
ancestor = opts.MainBranches.GetMergeBase(opts.RefName)
ancestor = self.getMergeBase(opts.RefName)
if opts.RefToShowDivergenceFrom != "" {
remoteAncestor = opts.MainBranches.GetMergeBase(opts.RefToShowDivergenceFrom)
remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom)
}
})
@@ -206,11 +211,11 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
authorEmail := split[3]
extraInfo := strings.TrimSpace(split[4])
parentHashes := split[5]
message := split[6]
divergence := models.DivergenceNone
if showDivergence {
divergence = lo.Ternary(split[6] == "<", models.DivergenceLeft, models.DivergenceRight)
divergence = lo.Ternary(split[7] == "<", models.DivergenceLeft, models.DivergenceRight)
}
message := split[7]
tags := []string{}
@@ -344,8 +349,6 @@ 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
@@ -468,6 +471,84 @@ 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)
i := i
branchName := branchName
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")
@@ -524,4 +605,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
return self.cmd.New(cmdArgs).DontLog()
}
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s`
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m`

View File

@@ -15,16 +15,16 @@ import (
"github.com/stretchr/testify/assert"
)
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|>|fix logging
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|>|refactor
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|>|WIP
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|>|WIP
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|>|WIP
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|>|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|>|refactoring the config struct`, "|", "\x00", -1)
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|fix logging
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|refactor
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|WIP
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|WIP
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|WIP
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode`, "|", "\x00", -1)
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
func TestGetCommits(t *testing.T) {
type scenario struct {
@@ -46,7 +46,7 @@ func TestGetCommits(t *testing.T) {
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -58,7 +58,7 @@ func TestGetCommits(t *testing.T) {
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -73,7 +73,7 @@ func TestGetCommits(t *testing.T) {
// here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches have an upstream
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
@@ -210,7 +210,7 @@ func TestGetCommits(t *testing.T) {
// here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
@@ -247,7 +247,7 @@ func TestGetCommits(t *testing.T) {
// here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
@@ -283,7 +283,7 @@ func TestGetCommits(t *testing.T) {
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -295,7 +295,7 @@ func TestGetCommits(t *testing.T) {
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -303,15 +303,15 @@ func TestGetCommits(t *testing.T) {
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(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: cmd,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
@@ -322,10 +322,8 @@ func TestGetCommits(t *testing.T) {
},
}
common.UserConfig().Git.MainBranches = scenario.mainBranches
opts := scenario.opts
opts.MainBranches = NewMainBranches(common, cmd)
commits, err := builder.GetCommits(opts)
common.UserConfig.Git.MainBranches = scenario.mainBranches
commits, err := builder.GetCommits(scenario.opts)
assert.Equal(t, scenario.expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err)

View File

@@ -30,10 +30,11 @@ func TestCommitRewordCommit(t *testing.T) {
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description).Run())
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description))
s.runner.CheckForMissingCalls()
})
}
@@ -99,6 +100,7 @@ func TestCommitCommitCmdObj(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.Commit.SignOff = s.configSignoff
@@ -134,6 +136,7 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.Commit.SignOff = s.configSignoff
@@ -168,6 +171,7 @@ func TestCommitCreateFixupCommit(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
s.test(instance.CreateFixupCommit(s.hash))
@@ -217,6 +221,7 @@ func TestCommitCreateAmendCommit(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
err := instance.CreateAmendCommit(s.originalSubject, s.newSubject, s.newDescription, s.includeFileChanges)
@@ -228,80 +233,65 @@ func TestCommitCreateAmendCommit(t *testing.T) {
func TestCommitShowCmdObj(t *testing.T) {
type scenario struct {
testName string
filterPath string
contextSize uint64
similarityThreshold int
ignoreWhitespace bool
extDiffCmd string
expected []string
testName string
filterPath string
contextSize int
ignoreWhitespace bool
extDiffCmd string
expected []string
}
scenarios := []scenario{
{
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 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 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: "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: "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 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 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, 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, 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%"},
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"},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd
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{
@@ -344,6 +334,7 @@ func TestGetCommitMsg(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{
runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil),
@@ -383,6 +374,7 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})

View File

@@ -43,7 +43,7 @@ func (self *ConfigCommands) ConfiguredPager() string {
}
func (self *ConfigCommands) GetPager(width int) string {
useConfig := self.UserConfig().Git.Paging.UseConfig
useConfig := self.UserConfig.Git.Paging.UseConfig
if useConfig {
pager := self.ConfiguredPager()
return strings.Split(pager, "| less")[0]
@@ -53,14 +53,14 @@ func (self *ConfigCommands) GetPager(width int) string {
"columnWidth": strconv.Itoa(width/2 - 6),
}
pagerTemplate := string(self.UserConfig().Git.Paging.Pager)
pagerTemplate := string(self.UserConfig.Git.Paging.Pager)
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
}
// UsingGpg tells us whether the user has gpg enabled so that we can know
// whether we need to run a subprocess to allow them to enter their password
func (self *ConfigCommands) UsingGpg() bool {
overrideGpg := self.UserConfig().Git.OverrideGpg
overrideGpg := self.UserConfig.Git.OverrideGpg
if overrideGpg {
return false
}

View File

@@ -58,9 +58,9 @@ func buildGitCommon(deps commonDeps) *GitCommon {
}
gitCommon.cmd = cmd
gitCommon.Common.SetUserConfig(deps.userConfig)
if gitCommon.Common.UserConfig() == nil {
gitCommon.Common.SetUserConfig(config.GetDefaultConfig())
gitCommon.Common.UserConfig = deps.userConfig
if gitCommon.Common.UserConfig == nil {
gitCommon.Common.UserConfig = config.GetDefaultConfig()
}
gitCommon.version = deps.gitVersion

View File

@@ -1,10 +1,6 @@
package git_commands
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
import "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
type DiffCommands struct {
*GitCommon
@@ -17,16 +13,10 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
}
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != ""
return self.cmd.New(
NewGitCmd("diff").
Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg("--submodule", "--no-ext-diff", "--color").
Arg(diffArgs...).
Dir(self.repoPaths.worktreePath).
ToArgv(),

View File

@@ -31,7 +31,7 @@ func (self *FileCommands) Cat(fileName string) (string, error) {
}
func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (string, error) {
editor := self.UserConfig().OS.EditCommand
editor := self.UserConfig.OS.EditCommand
if editor == "" {
editor = self.config.GetCoreEditor()
@@ -60,7 +60,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
"line": strconv.Itoa(lineNumber),
}
editCmdTemplate := self.UserConfig().OS.EditCommandTemplate
editCmdTemplate := self.UserConfig.OS.EditCommandTemplate
if len(editCmdTemplate) == 0 {
switch editor {
case "emacs", "nano", "vi", "vim", "nvim":
@@ -78,7 +78,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
// Legacy support for old config; to be removed at some point
if self.UserConfig().OS.Edit == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" {
// If multiple files are selected, we'll simply edit just the first one.
// It's not worth fixing this for the legacy support.
if cmdStr, err := self.GetEditCmdStrLegacy(filenames[0], 1); err == nil {
@@ -86,7 +86,7 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
}
}
template, suspend := config.GetEditTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) })
templateValues := map[string]string{
@@ -99,13 +99,13 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
// Legacy support for old config; to be removed at some point
if self.UserConfig().OS.EditAtLine == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if self.UserConfig.OS.EditAtLine == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr, true
}
}
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@@ -118,13 +118,13 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
// Legacy support for old config; to be removed at some point
if self.UserConfig().OS.EditAtLineAndWait == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if self.UserConfig.OS.EditAtLineAndWait == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr
}
}
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@@ -136,7 +136,7 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
}
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"dir": self.cmd.Quote(path),

View File

@@ -100,19 +100,15 @@ type FileStatus struct {
PreviousName string
}
func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
cmdArgs := NewGitCmd("status").
Arg(opts.UntrackedFilesArg).
Arg("--porcelain").
Arg("-z").
ArgIfElse(
opts.NoRenames,
"--no-renames",
fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold),
).
ArgIf(opts.NoRenames, "--no-renames").
ToArgv()
statusLines, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs()
statusLines, _, err := c.cmd.New(cmdArgs).DontLog().RunWithOutputs()
if err != nil {
return []FileStatus{}, err
}

View File

@@ -5,31 +5,27 @@ 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
similarityThreshold int
runner oscommands.ICmdObjRunner
expectedFiles []*models.File
testName string
runner oscommands.ICmdObjRunner
expectedFiles []*models.File
}
scenarios := []scenario{
{
"No files found",
50,
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "", nil),
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"}, "", nil),
[]*models.File{},
},
{
"Several files found",
50,
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
"MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
nil,
),
@@ -98,9 +94,8 @@ func TestFileGetStatusFiles(t *testing.T) {
},
{
"File with new line char",
50,
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil),
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"}, "MM a\nb.txt", nil),
[]*models.File{
{
Name: "a\nb.txt",
@@ -118,9 +113,8 @@ func TestFileGetStatusFiles(t *testing.T) {
},
{
"Renamed files",
50,
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
"R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
nil,
),
@@ -155,9 +149,8 @@ func TestFileGetStatusFiles(t *testing.T) {
},
{
"File with arrow in name",
50,
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
`?? a -> b.txt`,
nil,
),
@@ -179,14 +172,12 @@ func TestFileGetStatusFiles(t *testing.T) {
}
for _, s := range scenarios {
s := s
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{appState: appState}),
GitCommon: buildGitCommon(commonDeps{}),
cmd: cmd,
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
getFileType: func(string) string { return "file" },

View File

@@ -23,6 +23,7 @@ func TestStartCmdObj(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{})
@@ -68,6 +69,7 @@ func TestFinishCmdObj(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),

View File

@@ -1,127 +0,0 @@
package git_commands
import (
"strings"
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
)
type MainBranches struct {
c *common.Common
// Which of the configured main branches 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
previousMainBranches []string
cmd oscommands.ICmdObjBuilder
mutex *deadlock.Mutex
}
func NewMainBranches(
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
) *MainBranches {
return &MainBranches{
c: cmn,
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()
configuredMainBranches := self.c.UserConfig().Git.MainBranches
if self.existingMainBranches == nil || !utils.EqualSlices(self.previousMainBranches, configuredMainBranches) {
self.existingMainBranches = self.determineMainBranches(configuredMainBranches)
self.previousMainBranches = configuredMainBranches
}
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(configuredMainBranches []string) []string {
var existingBranches []string
var wg sync.WaitGroup
existingBranches = make([]string, len(configuredMainBranches))
for i, branchName := range 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
}

View File

@@ -47,8 +47,8 @@ type ApplyPatchOpts struct {
Reverse bool
}
func (self *PatchCommands) ApplyCustomPatch(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) error {
patch := self.PatchBuilder.PatchToApply(reverse, turnAddedFilesIntoDiffAgainstEmptyFile)
func (self *PatchCommands) ApplyCustomPatch(reverse bool) error {
patch := self.PatchBuilder.PatchToApply(reverse)
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, true); err != nil {
if err := self.ApplyCustomPatch(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, false); err != nil {
if err := self.ApplyCustomPatch(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, true); err != nil {
if err := self.ApplyCustomPatch(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, true); err != nil {
if err := self.ApplyCustomPatch(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, true); err != nil {
if err := self.ApplyCustomPatch(true); err != nil {
_ = self.rebase.AbortRebase()
return err
}

View File

@@ -35,8 +35,9 @@ func NewRebaseCommands(
}
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
if self.config.UsingGpg() {
return errors.New(self.Tr.DisabledForGPG)
if models.IsHeadCommit(commits, index) {
// we've selected the top commit so no rebase is required
return self.commit.RewordLastCommit(summary, description)
}
err := self.BeginInteractiveRebaseForCommit(commits, index, false)
@@ -45,7 +46,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su
}
// now the selected commit should be our head so we'll amend it with the new message
err = self.commit.RewordLastCommit(summary, description).Run()
err = self.commit.RewordLastCommit(summary, description)
if err != nil {
return err
}
@@ -66,47 +67,42 @@ func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index
}), nil
}
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, start, end int) error {
return self.GenericAmend(commits, start, end, func(_ *models.Commit) error {
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
return self.GenericAmend(commits, index, func() error {
return self.commit.ResetAuthor()
})
}
func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, start, end int, value string) error {
return self.GenericAmend(commits, start, end, func(_ *models.Commit) error {
func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int, value string) error {
return self.GenericAmend(commits, index, func() error {
return self.commit.SetAuthor(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) 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) GenericAmend(commits []*models.Commit, start, end int, f func(commit *models.Commit) error) error {
if start == end && models.IsHeadCommit(commits, start) {
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
if models.IsHeadCommit(commits, index) {
// we've selected the top commit so no rebase is required
return f(commits[start])
return f()
}
err := self.BeginInteractiveRebaseForCommitRange(commits, start, end, false)
err := self.BeginInteractiveRebaseForCommit(commits, index, false)
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
}
// now the selected commit should be our head so we'll amend it
err = f()
if err != nil {
return err
}
return nil
return self.ContinueRebase()
}
func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error {
@@ -145,11 +141,11 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx
baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex)
changes := lo.FilterMap(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) (daemon.ChangeTodoAction, bool) {
changes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) daemon.ChangeTodoAction {
return daemon.ChangeTodoAction{
Hash: commit.Hash,
NewAction: action,
}, !commit.IsMerge()
}
})
self.os.LogCommand(logTodoChanges(changes), false)
@@ -284,11 +280,6 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
return cmdObj.Run()
}
func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) {
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
// AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
commit := commits[commitIndex]
@@ -297,7 +288,9 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
return err
}
fixupHash, err := self.getHashOfLastCommitMade()
// Get the hash of the commit we just created
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput()
if err != nil {
return err
}
@@ -305,20 +298,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash, true),
}).Run()
}
func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error {
fixupHash, err := self.getHashOfLastCommitMade()
if err != nil {
return err
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash, fixupHash, false),
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash),
}).Run()
}
@@ -401,13 +381,7 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
func (self *RebaseCommands) BeginInteractiveRebaseForCommit(
commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool,
) error {
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 {
if len(commits)-1 < commitIndex {
return errors.New("index outside of range of commits")
}
@@ -418,17 +392,14 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
return errors.New(self.Tr.DisabledForGPG)
}
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,
})
}
changes := []daemon.ChangeTodoAction{{
Hash: commits[commitIndex].Hash,
NewAction: todo.Edit,
}}
self.os.LogCommand(logTodoChanges(changes), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, end+1),
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
overrideEditor: true,
keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty,
instruction: daemon.NewChangeTodoActionsInstruction(changes),

View File

@@ -67,6 +67,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion})
s.test(instance.RebaseBranch(s.arg))
@@ -88,6 +89,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
`^GIT_SEQUENCE_EDITOR=.*$`,
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
} {
regexStr := regexStr
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
return regexp.MustCompile(regexStr).MatchString(envVar)
})
@@ -161,6 +163,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{
runner: s.runner,

View File

@@ -176,6 +176,7 @@ func TestGetReflogCommits(t *testing.T) {
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) {
builder := &ReflogCommitLoader{
Common: utils.NewDummyCommon(),

View File

@@ -2,7 +2,7 @@ package git_commands
import (
ioFs "io/fs"
"os"
"path"
"path/filepath"
"strings"
@@ -18,7 +18,6 @@ type RepoPaths struct {
repoPath string
repoGitDirPath string
repoName string
isBareRepo bool
}
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
@@ -55,19 +54,14 @@ 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{
worktreePath: currentPath,
worktreeGitDirPath: filepath.Join(currentPath, ".git"),
worktreeGitDirPath: path.Join(currentPath, ".git"),
repoPath: currentPath,
repoGitDirPath: filepath.Join(currentPath, ".git"),
repoGitDirPath: path.Join(currentPath, ".git"),
repoName: "lazygit",
isBareRepo: false,
}
}
@@ -75,19 +69,7 @@ func GetRepoPaths(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
) (*RepoPaths, error) {
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")
gitDirOutput, err := callGitRevParse(cmd, version, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree")
if err != nil {
return nil, err
}
@@ -102,22 +84,21 @@ func GetRepoPathsForDir(
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 5. In that case
// a value, meaning gitDirResults will be length 4. 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) == 5
isSubmodule := len(gitDirResults) == 4
var repoPath string
if isSubmodule {
repoPath = worktreePath
} else {
repoPath = filepath.Dir(repoGitDirPath)
repoPath = path.Dir(repoGitDirPath)
}
repoName := filepath.Base(repoPath)
repoName := path.Base(repoPath)
return &RepoPaths{
worktreePath: worktreePath,
@@ -125,10 +106,17 @@ func GetRepoPathsForDir(
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,
@@ -153,7 +141,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
result := []string{}
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// That file points us to the `.git` file in the worktree.
worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees")
worktreeGitDirsPath := path.Join(repoGitDirPath, "worktrees")
// ensure the directory exists
_, err := fs.Stat(worktreeGitDirsPath)
@@ -170,7 +158,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
return nil
}
gitDirPath := filepath.Join(currPath, "gitdir")
gitDirPath := path.Join(currPath, "gitdir")
gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
if err != nil {
// ignoring error
@@ -178,7 +166,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
}
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
// removing the .git part
worktreeDir := filepath.Dir(trimmedGitDir)
worktreeDir := path.Dir(trimmedGitDir)
result = append(result, worktreeDir)
return nil
})

View File

@@ -2,13 +2,11 @@ package git_commands
import (
"fmt"
"runtime"
"strings"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
@@ -31,152 +29,63 @@ func TestGetRepoPaths(t *testing.T) {
Name: "typical case",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir
`C:\path\to\repo\.git`,
// --git-common-dir
`C:\path\to\repo\.git`,
// --is-bare-repository
"false",
// --show-superproject-working-tree
}, []string{
expectedOutput := []string{
// --show-toplevel
"/path/to/repo",
// --git-dir
"/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", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(mockOutput, "\n"),
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\repo\.git`,
repoPath: `C:\path\to\repo`,
repoGitDirPath: `C:\path\to\repo\.git`,
repoName: `repo`,
isBareRepo: false,
}, &RepoPaths{
Expected: &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/repo/.git",
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
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir
`C:\path\to\bare_repo\bare.git`,
// --git-common-dir
`C:\path\to\bare_repo\bare.git`,
// --is-bare-repository
`true`,
// --show-superproject-working-tree
}, []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(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\bare_repo\bare.git`,
repoPath: `C:\path\to\bare_repo`,
repoGitDirPath: `C:\path\to\bare_repo\bare.git`,
repoName: `bare_repo`,
isBareRepo: true,
}, &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,
},
{
Name: "submodule",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo\submodule1`,
// --git-dir
`C:\path\to\repo\.git\modules\submodule1`,
// --git-common-dir
`C:\path\to\repo\.git\modules\submodule1`,
// --is-bare-repository
`false`,
// --show-superproject-working-tree
`C:\path\to\repo`,
}, []string{
expectedOutput := []string{
// --show-toplevel
"/path/to/repo/submodule1",
// --git-dir
"/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", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(mockOutput, "\n"),
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
nil)
},
Path: "/path/to/repo/submodule1",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo\submodule1`,
worktreeGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
repoPath: `C:\path\to\repo\submodule1`,
repoGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
repoName: `submodule1`,
isBareRepo: false,
}, &RepoPaths{
Expected: &RepoPaths{
worktreePath: "/path/to/repo/submodule1",
worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoPath: "/path/to/repo/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoName: "submodule1",
isBareRepo: false,
}),
},
Err: nil,
},
{
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", "--is-bare-repository", "--show-superproject-working-tree"),
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
"",
errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git"))
},
@@ -185,13 +94,14 @@ 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 --is-bare-repository --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 --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.Name, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t)
cmd := oscommands.NewDummyCmdObjBuilder(runner)
@@ -211,7 +121,7 @@ func TestGetRepoPaths(t *testing.T) {
// prepare the filesystem for the scenario
s.BeforeFunc(runner, getRevParseArgs)
repoPaths, err := GetRepoPathsForDir("", cmd, version)
repoPaths, err := GetRepoPaths(cmd, version)
// check the error and the paths
if s.Err != nil {

View File

@@ -84,10 +84,9 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
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()
@@ -122,22 +121,9 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
return nil
}
// SaveStagedChanges stashes only the currently staged changes.
// SaveStagedChanges stashes only the currently staged changes. This takes a few steps
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func (self *StashCommands) SaveStagedChanges(message string) error {
if self.version.IsAtLeast(2, 35, 0) {
return self.cmd.New(NewGitCmd("stash").Arg("push").Arg("--staged").Arg("-m", message).ToArgv()).Run()
}
// Git versions older than 2.35.0 don't support the --staged flag, so we
// need to fall back to a more complex solution.
// Shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
//
// Note that this method has a few bugs:
// - it fails when there are *only* staged changes
// - it fails when staged and unstaged changes within a single file are too close together
// We don't bother fixing these, because users can simply update git when
// they are affected by these issues.
// wrap in 'writing', which uses a mutex
if err := self.cmd.New(
NewGitCmd("stash").Arg("--keep-index").ToArgv(),

View File

@@ -47,6 +47,7 @@ func TestGetStashEntries(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)

View File

@@ -74,6 +74,7 @@ func TestStashStore(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expected, "", nil)
@@ -98,56 +99,44 @@ func TestStashHash(t *testing.T) {
func TestStashStashEntryCmdObj(t *testing.T) {
type scenario struct {
testName string
index int
contextSize uint64
similarityThreshold int
ignoreWhitespace bool
expected []string
testName string
index int
contextSize int
ignoreWhitespace bool
expected []string
}
scenarios := []scenario{
{
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: "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: "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: "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 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}"},
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}"},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
appState.RenameSimilarityThreshold = s.similarityThreshold
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
@@ -192,6 +181,7 @@ func TestStashRename(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expectedHashCmd, s.hashResult, nil).

View File

@@ -3,8 +3,10 @@ package git_commands
import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
)
@@ -47,8 +49,20 @@ func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
return enums.REBASE_MODE_NONE
}
func (self *StatusCommands) IsBareRepo() bool {
return self.repoPaths.isBareRepo
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) IsInNormalRebase() (bool, error) {

View File

@@ -19,7 +19,6 @@ func NewSyncCommands(gitCommon *GitCommon) *SyncCommands {
// Push pushes to a branch
type PushOpts struct {
Force bool
ForceWithLease bool
UpstreamRemote string
UpstreamBranch string
SetUpstream bool
@@ -31,11 +30,10 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
}
cmdArgs := NewGitCmd("push").
ArgIf(opts.Force, "--force").
ArgIf(opts.ForceWithLease, "--force-with-lease").
ArgIf(opts.Force, "--force-with-lease").
ArgIf(opts.SetUpstream, "--set-upstream").
ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
ArgIf(opts.UpstreamBranch != "", "HEAD:"+opts.UpstreamBranch).
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
@@ -60,7 +58,7 @@ func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder
}
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.PromptOnCredentialRequest(task)
@@ -72,7 +70,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
}
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
@@ -95,7 +93,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
Arg("--no-edit").
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.RemoteName != "", opts.RemoteName).
ArgIf(opts.BranchName != "", "refs/heads/"+opts.BranchName).
ArgIf(opts.BranchName != "", opts.BranchName).
GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir).
ToArgv()
@@ -112,7 +110,7 @@ func (self *SyncCommands) FastForward(
) error {
cmdArgs := self.fetchCommandBuilder(false).
Arg(remoteName).
Arg("refs/heads/" + remoteBranchName + ":" + branchName).
Arg(remoteBranchName + ":" + branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()

View File

@@ -18,70 +18,62 @@ func TestSyncPush(t *testing.T) {
scenarios := []scenario{
{
testName: "Push with force disabled",
opts: PushOpts{ForceWithLease: false},
opts: PushOpts{Force: 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"})
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
assert.NoError(t, err)
},
},
{
testName: "Push with force disabled, upstream supplied",
opts: PushOpts{
ForceWithLease: false,
Force: false,
UpstreamRemote: "origin",
UpstreamBranch: "master",
},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "HEAD:master"})
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "master"})
assert.NoError(t, err)
},
},
{
testName: "Push with force disabled, setting upstream",
opts: PushOpts{
ForceWithLease: false,
Force: 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", "HEAD:master"})
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "master"})
assert.NoError(t, err)
},
},
{
testName: "Push with force-with-lease enabled, setting upstream",
testName: "Push with force enabled, setting upstream",
opts: PushOpts{
ForceWithLease: true,
Force: 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", "HEAD:master"})
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "master"})
assert.NoError(t, err)
},
},
{
testName: "Push with remote branch but no origin",
opts: PushOpts{
ForceWithLease: true,
Force: true,
UpstreamRemote: "",
UpstreamBranch: "master",
SetUpstream: true,
@@ -94,6 +86,7 @@ func TestSyncPush(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
task := gocui.NewFakeTask()
@@ -131,9 +124,10 @@ func TestSyncFetch(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
task := gocui.NewFakeTask()
s.test(instance.FetchCmdObj(task))
})
@@ -169,9 +163,10 @@ func TestSyncFetchBackground(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
s.test(instance.FetchBackgroundCmdObj())
})
}

View File

@@ -44,6 +44,7 @@ func TestGetTags(t *testing.T) {
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) {
loader := &TagLoader{
Common: utils.NewDummyCommon(),

View File

@@ -3,7 +3,7 @@ package git_commands
import (
"fmt"
"os"
"path/filepath"
"path"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -233,7 +233,7 @@ func (self *WorkingTreeCommands) Ignore(filename string) error {
// Exclude adds a file to the .git/info/exclude for the repo
func (self *WorkingTreeCommands) Exclude(filename string) error {
excludeFile := filepath.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
excludeFile := path.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
return self.os.AppendLineToFile(excludeFile, filename)
}
@@ -245,7 +245,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
}
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
colorArg := self.UserConfig().Git.Paging.ColorArg
colorArg := self.UserConfig.Git.Paging.ColorArg
if plain {
colorArg = "never"
}
@@ -253,7 +253,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
contextSize := self.AppState.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").
@@ -263,7 +263,6 @@ 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("--").
@@ -285,12 +284,12 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
colorArg := self.UserConfig().Git.Paging.ColorArg
colorArg := self.UserConfig.Git.Paging.ColorArg
if plain {
colorArg = "never"
}
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").
@@ -364,7 +363,7 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
return self.RemoveUntrackedFiles()
}
// ResetHard runs `git reset --hard`
// ResetHardHead runs `git reset --hard`
func (self *WorkingTreeCommands) ResetHard(ref string) error {
cmdArgs := NewGitCmd("reset").Arg("--hard", ref).
ToArgv()

View File

@@ -61,6 +61,7 @@ func TestWorkingTreeUnstageFile(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.UnStageFile([]string{"test.txt"}, s.reset))
@@ -189,6 +190,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, removeFile: s.removeFile})
err := instance.DiscardAllFileChanges(s.file)
@@ -205,14 +207,13 @@ 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 uint64
similarityThreshold int
runner *oscommands.FakeCmdObjRunner
testName string
file *models.File
plain bool
cached bool
ignoreWhitespace bool
contextSize int
runner *oscommands.FakeCmdObjRunner
}
const expectedResult = "pretend this is an actual git diff"
@@ -225,13 +226,12 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
similarityThreshold: 50,
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "cached",
@@ -240,13 +240,12 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: true,
ignoreWhitespace: false,
contextSize: 3,
similarityThreshold: 50,
plain: false,
cached: true,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--cached", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "plain",
@@ -255,13 +254,12 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
plain: true,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
similarityThreshold: 50,
plain: true,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "File not tracked and file has no staged changes",
@@ -270,13 +268,12 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: false,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
similarityThreshold: 50,
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
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),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
@@ -285,13 +282,12 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: true,
contextSize: 3,
similarityThreshold: 50,
plain: false,
cached: false,
ignoreWhitespace: true,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
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),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
@@ -300,38 +296,22 @@ func TestWorkingTreeDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 17,
similarityThreshold: 50,
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 17,
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=3", "--color=always", "--find-renames=33%", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
appState.RenameSimilarityThreshold = s.similarityThreshold
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
@@ -352,7 +332,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
reverse bool
plain bool
ignoreWhitespace bool
contextSize uint64
contextSize int
runner *oscommands.FakeCmdObjRunner
}
@@ -395,6 +375,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
appState := &config.AppState{}
@@ -447,6 +428,7 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
@@ -477,6 +459,7 @@ func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.DiscardUnstagedFileChanges(s.file))
@@ -504,6 +487,7 @@ func TestWorkingTreeDiscardAnyUnstagedFileChanges(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.DiscardAnyUnstagedFileChanges())
@@ -531,6 +515,7 @@ func TestWorkingTreeRemoveUntrackedFiles(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.RemoveUntrackedFiles())
@@ -560,6 +545,7 @@ func TestWorkingTreeResetHard(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.ResetHard(s.ref))

View File

@@ -76,6 +76,8 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
wg := sync.WaitGroup{}
wg.Add(len(worktrees))
for _, worktree := range worktrees {
worktree := worktree
go utils.Safe(func() {
defer wg.Done()

View File

@@ -181,6 +181,7 @@ branch refs/heads/mybranch-worktree
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t)
fs := afero.NewMemMapFs()

View File

@@ -42,7 +42,7 @@ func (self *CachedGitConfig) Get(key string) string {
defer self.mutex.Unlock()
if value, ok := self.cache[key]; ok {
self.log.Debug("using cache for key " + key)
self.log.Debugf("using cache for key " + key)
return value
}
@@ -56,7 +56,7 @@ func (self *CachedGitConfig) GetGeneral(args string) string {
defer self.mutex.Unlock()
if value, ok := self.cache[args]; ok {
self.log.Debug("using cache for args " + args)
self.log.Debugf("using cache for args " + args)
return value
}
@@ -69,7 +69,7 @@ func (self *CachedGitConfig) getGeneralAux(args string) string {
cmd := getGitConfigGeneralCmd(args)
value, err := self.runGitConfigCmd(cmd)
if err != nil {
self.log.Debugf("Error getting git config value for args: %s. Error: %v", args, err.Error())
self.log.Debugf("Error getting git config value for args: " + args + ". Error: " + err.Error())
return ""
}
return strings.TrimSpace(value)
@@ -79,7 +79,7 @@ func (self *CachedGitConfig) getAux(key string) string {
cmd := getGitConfigCmd(key)
value, err := self.runGitConfigCmd(cmd)
if err != nil {
self.log.Debugf("Error getting git config value for key: %s. Error: %v", key, err.Error())
self.log.Debugf("Error getting git config value for key: " + key + ". Error: " + err.Error())
return ""
}
return strings.TrimSpace(value)

View File

@@ -50,6 +50,7 @@ func TestGetBool(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
fake := NewFakeGitConfig(s.mockResponses)
real := NewCachedGitConfig(
@@ -86,6 +87,7 @@ func TestGet(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
fake := NewFakeGitConfig(s.mockResponses)
real := NewCachedGitConfig(

View File

@@ -413,10 +413,11 @@ func TestGetPullRequestURL(t *testing.T) {
}
for _, s := range scenarios {
s := s
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)
})

View File

@@ -1,9 +1,6 @@
package models
import (
"fmt"
"sync/atomic"
)
import "fmt"
// Branch : A git branch
// duplicating this for now
@@ -13,14 +10,10 @@ type Branch struct {
DisplayName string
// indicator of when the branch was last checked out e.g. '2d', '3m'
Recency string
// how many commits ahead we are from the remote branch (how many commits we can push, assuming we push to our tracked remote branch)
AheadForPull string
// how many commits ahead we are from the remote branch (how many commits we can push)
Pushables string
// how many commits behind we are from the remote branch (how many commits we can pull)
BehindForPull string
// how many commits ahead we are from the branch we're pushing to (which might not be the same as our upstream branch in a triangular workflow)
AheadForPush string
// how many commits behind we are from the branch we're pushing to (which might not be the same as our upstream branch in a triangular workflow)
BehindForPush string
Pullables string
// whether the remote branch is 'gone' i.e. we're tracking a remote branch that has been deleted
UpstreamGone bool
// whether this is the current branch. Exactly one branch should have this be true
@@ -35,11 +28,6 @@ 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 {
@@ -53,10 +41,6 @@ func (b *Branch) RefName() string {
return b.Name
}
func (b *Branch) ShortRefName() string {
return b.RefName()
}
func (b *Branch) ParentRefName() string {
return b.RefName() + "^"
}
@@ -96,30 +80,26 @@ func (b *Branch) IsTrackingRemote() bool {
// we know that the remote branch is not stored locally based on our pushable/pullable
// count being question marks.
func (b *Branch) RemoteBranchStoredLocally() bool {
return b.IsTrackingRemote() && b.AheadForPull != "?" && b.BehindForPull != "?"
return b.IsTrackingRemote() && b.Pushables != "?" && b.Pullables != "?"
}
func (b *Branch) RemoteBranchNotStoredLocally() bool {
return b.IsTrackingRemote() && b.AheadForPull == "?" && b.BehindForPull == "?"
return b.IsTrackingRemote() && b.Pushables == "?" && b.Pullables == "?"
}
func (b *Branch) MatchesUpstream() bool {
return b.RemoteBranchStoredLocally() && b.AheadForPull == "0" && b.BehindForPull == "0"
return b.RemoteBranchStoredLocally() && b.Pushables == "0" && b.Pullables == "0"
}
func (b *Branch) IsAheadForPull() bool {
return b.RemoteBranchStoredLocally() && b.AheadForPull != "0"
func (b *Branch) HasCommitsToPush() bool {
return b.RemoteBranchStoredLocally() && b.Pushables != "0"
}
func (b *Branch) IsBehindForPull() bool {
return b.RemoteBranchStoredLocally() && b.BehindForPull != "0"
}
func (b *Branch) IsBehindForPush() bool {
return b.RemoteBranchStoredLocally() && b.BehindForPush != "0"
func (b *Branch) HasCommitsToPull() bool {
return b.RemoteBranchStoredLocally() && b.Pullables != "0"
}
// for when we're in a detached head state
func (b *Branch) IsRealBranch() bool {
return b.AheadForPull != "" && b.BehindForPull != ""
return b.Pushables != "" && b.Pullables != ""
}

View File

@@ -70,10 +70,6 @@ func (c *Commit) RefName() string {
return c.Hash
}
func (c *Commit) ShortRefName() string {
return c.Hash[:7]
}
func (c *Commit) ParentRefName() string {
if c.IsFirstCommit() {
return EmptyTreeCommitHash

View File

@@ -18,10 +18,6 @@ func (r *RemoteBranch) RefName() string {
return r.FullName()
}
func (r *RemoteBranch) ShortRefName() string {
return r.RefName()
}
func (r *RemoteBranch) ParentRefName() string {
return r.RefName() + "^"
}

View File

@@ -17,10 +17,6 @@ func (s *StashEntry) RefName() string {
return fmt.Sprintf("stash@{%d}", s.Index)
}
func (s *StashEntry) ShortRefName() string {
return s.RefName()
}
func (s *StashEntry) ParentRefName() string {
return s.RefName() + "^"
}

View File

@@ -16,10 +16,6 @@ func (t *Tag) RefName() string {
return t.Name
}
func (t *Tag) ShortRefName() string {
return t.RefName()
}
func (t *Tag) ParentRefName() string {
return t.RefName() + "^"
}

View File

@@ -14,8 +14,6 @@ type ICmdObjBuilder interface {
New(args []string) ICmdObj
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
NewShell(commandStr string) ICmdObj
// Like NewShell, but uses the user's shell rather than "bash", and passes -i to it
NewInteractiveShell(commandStr string) ICmdObj
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
Quote(str string) string
}
@@ -45,23 +43,10 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
}
func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
quotedCommand := self.quotedCommandString(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs)
}
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
quotedCommand := self.quotedCommandString(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs)
}
func (self *CmdObjBuilder) quotedCommandString(commandStr string) string {
var quotedCommand string
// Windows does not seem to like quotes around the command
if self.platform.OS == "windows" {
return strings.NewReplacer(
quotedCommand = strings.NewReplacer(
"^", "^^",
"&", "^&",
"|", "^|",
@@ -69,9 +54,13 @@ func (self *CmdObjBuilder) quotedCommandString(commandStr string) string {
">", "^>",
"%", "^%",
).Replace(commandStr)
} else {
quotedCommand = self.Quote(commandStr)
}
return self.Quote(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs)
}
func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {

View File

@@ -161,7 +161,7 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
}
scanner := bufio.NewScanner(stdoutPipe)
scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize))
scanner.Split(bufio.ScanLines)
if err := cmd.Start(); err != nil {
return err
}
@@ -178,11 +178,6 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
}
}
if scanner.Err() != nil {
_ = Kill(cmd)
return scanner.Err()
}
_ = cmd.Wait()
self.log.Infof("%s (%s)", cmdObj.ToString(), time.Since(t))
@@ -284,7 +279,6 @@ const (
Username
Passphrase
PIN
Token
)
// Whenever we're asked for a password we just enter a newline, which will
@@ -319,7 +313,7 @@ func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(Credential
}
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
// promptUserForCredential is a function that gets executed when this function detect you need to fill in a password or passphrase
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
func (self *cmdObjRunner) runAndDetectCredentialRequest(
cmdObj ICmdObj,
@@ -377,7 +371,6 @@ 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{}

View File

@@ -39,8 +39,6 @@ func TestProcessOutput(t *testing.T) {
return "passphrase"
case PIN:
return "pin"
case Token:
return "token"
default:
panic("unexpected credential type")
}
@@ -94,12 +92,6 @@ 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,

View File

@@ -51,13 +51,11 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
}
var dummyPlatform = &Platform{
OS: "darwin",
Shell: "bash",
InteractiveShell: "bash",
ShellArg: "-c",
InteractiveShellArg: "-i",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
OS: "darwin",
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {

View File

@@ -35,13 +35,11 @@ type OSCommand struct {
// Platform stores the os state
type Platform struct {
OS string
Shell string
InteractiveShell string
ShellArg string
InteractiveShellArg string
OpenCommand string
OpenLinkCommand string
OS string
Shell string
ShellArg string
OpenCommand string
OpenLinkCommand string
}
// NewOSCommand os command runner
@@ -80,10 +78,10 @@ func FileType(path string) string {
}
func (c *OSCommand) OpenFile(filename string) error {
commandTemplate := c.UserConfig().OS.Open
commandTemplate := c.UserConfig.OS.Open
if commandTemplate == "" {
// Legacy support
commandTemplate = c.UserConfig().OS.OpenCommand
commandTemplate = c.UserConfig.OS.OpenCommand
}
if commandTemplate == "" {
commandTemplate = config.GetPlatformDefaultConfig().Open
@@ -96,10 +94,10 @@ func (c *OSCommand) OpenFile(filename string) error {
}
func (c *OSCommand) OpenLink(link string) error {
commandTemplate := c.UserConfig().OS.OpenLink
commandTemplate := c.UserConfig.OS.OpenLink
if commandTemplate == "" {
// Legacy support
commandTemplate = c.UserConfig().OS.OpenLinkCommand
commandTemplate = c.UserConfig.OS.OpenLinkCommand
}
if commandTemplate == "" {
commandTemplate = config.GetPlatformDefaultConfig().OpenLink
@@ -241,13 +239,14 @@ func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
wg.Add(len(cmds))
for _, cmd := range cmds {
currentCmd := cmd
go utils.Safe(func() {
stderr, err := cmd.StderrPipe()
stderr, err := currentCmd.StderrPipe()
if err != nil {
c.Log.Error(err)
}
if err := cmd.Start(); err != nil {
if err := currentCmd.Start(); err != nil {
c.Log.Error(err)
}
@@ -257,7 +256,7 @@ func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
}
}
if err := cmd.Wait(); err != nil {
if err := currentCmd.Wait(); err != nil {
c.Log.Error(err)
}
@@ -294,8 +293,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
},
)
c.LogCommand(msg, false)
if c.UserConfig().OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{
if c.UserConfig.OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig.OS.CopyToClipboardCmd, map[string]string{
"text": c.Cmd.Quote(str),
})
return c.Cmd.NewShell(cmdStr).Run()
@@ -304,23 +303,6 @@ 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,

View File

@@ -4,26 +4,15 @@
package oscommands
import (
"os"
"runtime"
)
func GetPlatform() *Platform {
return &Platform{
OS: runtime.GOOS,
Shell: "bash",
InteractiveShell: getUserShell(),
ShellArg: "-c",
InteractiveShellArg: "-i",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
OS: runtime.GOOS,
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}
}
func getUserShell() string {
if shell := os.Getenv("SHELL"); shell != "" {
return shell
}
return "bash"
}

View File

@@ -75,7 +75,7 @@ func TestOSCommandOpenFileDarwin(t *testing.T) {
for _, s := range scenarios {
oSCmd := NewDummyOSCommandWithRunner(s.runner)
oSCmd.Platform.OS = "darwin"
oSCmd.UserConfig().OS.Open = "open {{filename}}"
oSCmd.UserConfig.OS.Open = "open {{filename}}"
s.test(oSCmd.OpenFile(s.filename))
}
@@ -135,7 +135,7 @@ func TestOSCommandOpenFileLinux(t *testing.T) {
for _, s := range scenarios {
oSCmd := NewDummyOSCommandWithRunner(s.runner)
oSCmd.Platform.OS = "linux"
oSCmd.UserConfig().OS.Open = `xdg-open {{filename}} > /dev/null`
oSCmd.UserConfig.OS.Open = `xdg-open {{filename}} > /dev/null`
s.test(oSCmd.OpenFile(s.filename))
}

View File

@@ -2,10 +2,8 @@ package oscommands
func GetPlatform() *Platform {
return &Platform{
OS: "windows",
Shell: "cmd",
InteractiveShell: "cmd",
ShellArg: "/c",
InteractiveShellArg: "",
OS: "windows",
Shell: "cmd",
ShellArg: "/c",
}
}

View File

@@ -71,7 +71,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
}
oSCmd.Platform = platform
oSCmd.Cmd.platform = platform
oSCmd.UserConfig().OS.OpenCommand = `start "" {{filename}}`
oSCmd.UserConfig.OS.OpenCommand = `start "" {{filename}}`
s.test(oSCmd.OpenFile(s.filename))
}

View File

@@ -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, turnAddedFilesIntoDiffAgainstEmptyFile bool) string {
func (p *PatchBuilder) PatchToApply(reverse bool) string {
patch := ""
for filename, info := range p.fileInfoMap {
@@ -73,12 +73,7 @@ func (p *PatchBuilder) PatchToApply(reverse bool, turnAddedFilesIntoDiffAgainstE
continue
}
patch += p.RenderPatchForFile(RenderPatchForFileOpts{
Filename: filename,
Plain: true,
Reverse: reverse,
TurnAddedFilesIntoDiffAgainstEmptyFile: turnAddedFilesIntoDiffAgainstEmptyFile,
})
patch += p.RenderPatchForFile(filename, true, reverse)
}
return patch
@@ -177,15 +172,8 @@ func (p *PatchBuilder) RemoveFileLineRange(filename string, firstLineIdx, lastLi
return nil
}
type RenderPatchForFileOpts struct {
Filename string
Plain bool
Reverse bool
TurnAddedFilesIntoDiffAgainstEmptyFile bool
}
func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
info, err := p.getFileInfo(opts.Filename)
func (p *PatchBuilder) RenderPatchForFile(filename string, plain bool, reverse bool) string {
info, err := p.getFileInfo(filename)
if err != nil {
p.Log.Error(err)
return ""
@@ -195,7 +183,7 @@ func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
return ""
}
if info.mode == WHOLE && opts.Plain {
if info.mode == WHOLE && 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.
@@ -204,12 +192,11 @@ func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
patch := Parse(info.diff).
Transform(TransformOpts{
Reverse: opts.Reverse,
TurnAddedFilesIntoDiffAgainstEmptyFile: opts.TurnAddedFilesIntoDiffAgainstEmptyFile,
IncludedLineIndices: info.includedLineIndices,
Reverse: reverse,
IncludedLineIndices: info.includedLineIndices,
})
if opts.Plain {
if plain {
return patch.FormatPlain()
} else {
return patch.FormatView(FormatViewOpts{})
@@ -222,12 +209,7 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
sort.Strings(filenames)
patches := lo.Map(filenames, func(filename string, _ int) string {
return p.RenderPatchForFile(RenderPatchForFileOpts{
Filename: filename,
Plain: plain,
Reverse: false,
TurnAddedFilesIntoDiffAgainstEmptyFile: true,
})
return p.RenderPatchForFile(filename, plain, false)
})
output := lo.Filter(patches, func(patch string, _ int) bool {
return patch != ""

View File

@@ -509,6 +509,7 @@ func TestTransform(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
lineIndices := ExpandRange(s.firstLineIndex, s.lastLineIndex)
@@ -565,6 +566,7 @@ func TestParseAndFormatPlain(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
// here we parse the patch, then format it, and ensure the result
// matches the original patch. Note that unified diffs allow omitting
@@ -602,6 +604,7 @@ func TestLineNumberOfLine(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
for i, idx := range s.indexes {
patch := Parse(s.patchStr)
@@ -630,6 +633,7 @@ func TestGetNextStageableLineIndex(t *testing.T) {
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
for i, idx := range s.indexes {
patch := Parse(s.patchStr)

View File

@@ -1,10 +1,6 @@
package patch
import (
"strings"
"github.com/samber/lo"
)
import "github.com/samber/lo"
type patchTransformer struct {
patch *Patch
@@ -26,13 +22,6 @@ 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
}
@@ -72,18 +61,6 @@ 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
}

View File

@@ -1,8 +1,6 @@
package common
import (
"sync/atomic"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus"
@@ -13,18 +11,10 @@ import (
type Common struct {
Log *logrus.Entry
Tr *i18n.TranslationSet
userConfig atomic.Pointer[config.UserConfig]
UserConfig *config.UserConfig
AppState *config.AppState
Debug bool
// for interacting with the filesystem. We use afero rather than the default
// `os` package for the sake of mocking the filesystem in tests
Fs afero.Fs
}
func (c *Common) UserConfig() *config.UserConfig {
return c.userConfig.Load()
}
func (c *Common) SetUserConfig(userConfig *config.UserConfig) {
c.userConfig.Store(userConfig)
}

View File

@@ -2,31 +2,29 @@ package config
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/adrg/xdg"
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3"
)
// AppConfig contains the base configuration fields required for lazygit.
type AppConfig struct {
debug bool `long:"debug" env:"DEBUG" default:"false"`
version string `long:"version" env:"VERSION" default:"unversioned"`
buildDate string `long:"build-date" env:"BUILD_DATE"`
name string `long:"name" env:"NAME" default:"lazygit"`
buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
userConfig *UserConfig
globalUserConfigFiles []*ConfigFile
userConfigFiles []*ConfigFile
userConfigDir string
tempDir string
appState *AppState
Debug bool `long:"debug" env:"DEBUG" default:"false"`
Version string `long:"version" env:"VERSION" default:"unversioned"`
BuildDate string `long:"build-date" env:"BUILD_DATE"`
Name string `long:"name" env:"NAME" default:"lazygit"`
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
UserConfig *UserConfig
UserConfigPaths []string
DeafultConfFiles bool
UserConfigDir string
TempDir string
AppState *AppState
IsNewRepo bool
}
type AppConfigurer interface {
@@ -40,29 +38,13 @@ type AppConfigurer interface {
GetUserConfig() *UserConfig
GetUserConfigPaths() []string
GetUserConfigDir() string
ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error
ReloadChangedUserConfigFiles() (error, bool)
ReloadUserConfig() error
GetTempDir() string
GetAppState() *AppState
SaveAppState() error
}
type ConfigFilePolicy int
const (
ConfigFilePolicyCreateIfMissing ConfigFilePolicy = iota
ConfigFilePolicyErrorIfMissing
ConfigFilePolicySkipIfMissing
)
type ConfigFile struct {
Path string
Policy ConfigFilePolicy
modDate time.Time
exists bool
}
// NewAppConfig makes a new app config
func NewAppConfig(
name string,
@@ -78,22 +60,17 @@ func NewAppConfig(
return nil, err
}
var configFiles []*ConfigFile
var userConfigPaths []string
customConfigFiles := os.Getenv("LG_CONFIG_FILE")
if customConfigFiles != "" {
// Load user defined config files
userConfigPaths := strings.Split(customConfigFiles, ",")
configFiles = lo.Map(userConfigPaths, func(path string, _ int) *ConfigFile {
return &ConfigFile{Path: path, Policy: ConfigFilePolicyErrorIfMissing}
})
userConfigPaths = strings.Split(customConfigFiles, ",")
} else {
// Load default config files
path := filepath.Join(configDir, ConfigFilename)
configFile := &ConfigFile{Path: path, Policy: ConfigFilePolicyCreateIfMissing}
configFiles = []*ConfigFile{configFile}
userConfigPaths = []string{filepath.Join(configDir, ConfigFilename)}
}
userConfig, err := loadUserConfigWithDefaults(configFiles)
userConfig, err := loadUserConfigWithDefaults(userConfigPaths)
if err != nil {
return nil, err
}
@@ -115,22 +92,26 @@ func NewAppConfig(
}
appConfig := &AppConfig{
name: name,
version: version,
buildDate: date,
debug: debuggingFlag,
buildSource: buildSource,
userConfig: userConfig,
globalUserConfigFiles: configFiles,
userConfigFiles: configFiles,
userConfigDir: configDir,
tempDir: tempDir,
appState: appState,
Name: name,
Version: version,
BuildDate: date,
Debug: debuggingFlag,
BuildSource: buildSource,
UserConfig: userConfig,
UserConfigPaths: userConfigPaths,
UserConfigDir: configDir,
TempDir: tempDir,
AppState: appState,
IsNewRepo: false,
}
return appConfig, nil
}
func isCustomConfigFile(path string) bool {
return path != filepath.Join(ConfigDir(), ConfigFilename)
}
func ConfigDir() string {
_, filePath := findConfigFile("config.yml")
@@ -142,48 +123,32 @@ func findOrCreateConfigDir() (string, error) {
return folder, os.MkdirAll(folder, 0o755)
}
func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error) {
func loadUserConfigWithDefaults(configFiles []string) (*UserConfig, error) {
return loadUserConfig(configFiles, GetDefaultConfig())
}
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
for _, configFile := range configFiles {
path := configFile.Path
statInfo, err := os.Stat(path)
if err == nil {
configFile.exists = true
configFile.modDate = statInfo.ModTime()
} else {
func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error) {
for _, path := range configFiles {
if _, err := os.Stat(path); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
switch configFile.Policy {
case ConfigFilePolicyErrorIfMissing:
// if use has supplied their own custom config file path(s), we assume
// the files have already been created, so we won't go and create them here.
if isCustomConfigFile(path) {
return nil, err
case ConfigFilePolicySkipIfMissing:
configFile.exists = false
continue
case ConfigFilePolicyCreateIfMissing:
file, err := os.Create(path)
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
continue
}
return nil, err
}
file.Close()
configFile.exists = true
statInfo, err := os.Stat(configFile.Path)
if err != nil {
return nil, err
}
configFile.modDate = statInfo.ModTime()
}
file, err := os.Create(path)
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
continue
}
return nil, err
}
file.Close()
}
content, err := os.ReadFile(path)
@@ -196,14 +161,10 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
return nil, err
}
existingCustomCommands := base.CustomCommands
if err := yaml.Unmarshal(content, base); err != nil {
return nil, fmt.Errorf("The config at `%s` couldn't be parsed, please inspect it before opening up an issue.\n%w", path, err)
}
base.CustomCommands = append(base.CustomCommands, existingCustomCommands...)
if err := base.Validate(); err != nil {
return nil, fmt.Errorf("The config at `%s` has a validation error.\n%w", path, err)
}
@@ -223,12 +184,6 @@ func migrateUserConfig(path string, content []byte) ([]byte, error) {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
}
changedContent, err = yaml_utils.RenameYamlKey(changedContent, []string{"keybinding", "universal", "executeCustomCommand"},
"executeShellCommand")
if err != nil {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
}
changedContent, err = changeNullKeybindingsToDisabled(changedContent)
if err != nil {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
@@ -259,81 +214,53 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
}
func (c *AppConfig) GetDebug() bool {
return c.debug
return c.Debug
}
func (c *AppConfig) GetVersion() string {
return c.version
return c.Version
}
func (c *AppConfig) GetName() string {
return c.name
return c.Name
}
// GetBuildSource returns the source of the build. For builds from goreleaser
// this will be binaryBuild
func (c *AppConfig) GetBuildSource() string {
return c.buildSource
return c.BuildSource
}
// GetUserConfig returns the user config
func (c *AppConfig) GetUserConfig() *UserConfig {
return c.userConfig
return c.UserConfig
}
// GetAppState returns the app state
func (c *AppConfig) GetAppState() *AppState {
return c.appState
return c.AppState
}
func (c *AppConfig) GetUserConfigPaths() []string {
return lo.FilterMap(c.userConfigFiles, func(f *ConfigFile, _ int) (string, bool) {
return f.Path, f.exists
})
return c.UserConfigPaths
}
func (c *AppConfig) GetUserConfigDir() string {
return c.userConfigDir
return c.UserConfigDir
}
func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error {
configFiles := append(c.globalUserConfigFiles, repoConfigFiles...)
userConfig, err := loadUserConfigWithDefaults(configFiles)
func (c *AppConfig) ReloadUserConfig() error {
userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths)
if err != nil {
return err
}
c.userConfig = userConfig
c.userConfigFiles = configFiles
c.UserConfig = userConfig
return nil
}
func (c *AppConfig) ReloadChangedUserConfigFiles() (error, bool) {
fileHasChanged := func(f *ConfigFile) bool {
info, err := os.Stat(f.Path)
if err != nil && !os.IsNotExist(err) {
// If we can't stat the file, assume it hasn't changed
return false
}
exists := err == nil
return exists != f.exists || (exists && info.ModTime() != f.modDate)
}
if lo.NoneBy(c.userConfigFiles, fileHasChanged) {
return nil, false
}
userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles)
if err != nil {
return err, false
}
c.userConfig = userConfig
return nil, true
}
func (c *AppConfig) GetTempDir() string {
return c.tempDir
return c.TempDir
}
// findConfigFile looks for a possibly existing config file.
@@ -372,9 +299,14 @@ func stateFilePath(filename string) (string, error) {
return xdg.StateFile(filepath.Join("lazygit", filename))
}
// ConfigFilename returns the filename of the default config file
func (c *AppConfig) ConfigFilename() string {
return filepath.Join(c.UserConfigDir, ConfigFilename)
}
// SaveAppState marshalls the AppState struct and writes it to the disk
func (c *AppConfig) SaveAppState() error {
marshalledAppState, err := yaml.Marshal(c.appState)
marshalledAppState, err := yaml.Marshal(c.AppState)
if err != nil {
return err
}
@@ -425,24 +357,6 @@ func loadAppState() (*AppState, error) {
return appState, nil
}
// SaveGlobalUserConfig saves the UserConfig back to disk. This is only used in
// integration tests, so we are a bit sloppy with error handling.
func (c *AppConfig) SaveGlobalUserConfig() {
if len(c.globalUserConfigFiles) != 1 {
panic("expected exactly one global user config file")
}
yamlContent, err := yaml.Marshal(c.userConfig)
if err != nil {
log.Fatalf("error marshalling user config: %v", err)
}
err = os.WriteFile(c.globalUserConfigFiles[0].Path, yamlContent, 0o644)
if err != nil {
log.Fatalf("error saving user config: %v", err)
}
}
// AppState stores data between runs of the app like when the last update check
// was performed and which other repos have been checked out
type AppState struct {
@@ -451,14 +365,11 @@ type AppState struct {
StartupPopupVersion int
LastVersion string // this is the last version the user was using, for the purpose of showing release notes
// these are for shell commands typed in directly, not for custom commands in the lazygit config.
// For backwards compatibility we keep the old name in yaml files.
ShellCommandsHistory []string `yaml:"customcommandshistory"`
// these are for custom commands typed in directly, not for custom commands in the lazygit config
CustomCommandsHistory []string
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize uint64
RenameSimilarityThreshold int
DiffContextSize int
LocalBranchSortOrder string
RemoteBranchSortOrder string
@@ -474,16 +385,15 @@ type AppState struct {
func getDefaultAppState() *AppState {
return &AppState{
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
LastUpdateCheck: 0,
RecentRepos: []string{},
StartupPopupVersion: 0,
LastVersion: "",
DiffContextSize: 3,
LocalBranchSortOrder: "recency",
RemoteBranchSortOrder: "alphabetical",
GitLogOrder: "", // should be "topo-order" eventually
GitLogShowGraph: "", // should be "always" eventually
}
}

Some files were not shown because too many files have changed in this diff Show More