Compare commits

..

73 Commits

Author SHA1 Message Date
Jesse Duffield
3557bc44ad Use double dash for disambuating path in editor templates 2023-07-29 18:34:43 +10:00
Jesse Duffield
201ec5307f Fix flakey worktree tests
In the presentation layer, when showing branches, we'll show worktrees against branches if they're
associated. But there was a race condition: if the worktree model was refreshed after the branches model,
it wouldn't be used in the presentation layer when it came time to render the branches.

A better solution would be to have some way of signalling that a particular context needs to be refreshed
and after all the models are done being refreshed, we then refresh the contexts. This will prevent
double-renders
2023-07-29 17:36:18 +10:00
Jesse Duffield
ab4f310ce9 rename files 2023-07-29 17:36:18 +10:00
Jesse Duffield
40753e916d Write unit tests with the help of afero
Afero is a package that lets you mock out a filesystem with an in-memory filesystem.
It allows us to easily create the files required for a given test without worrying about
a cleanup step or different tests tripping on eachother when run in parallel.

Later on I'll standardise on using afero over the vanilla os package
2023-07-29 17:36:18 +10:00
Jesse Duffield
72ec7922ac Fix bug where worktree view would take over window upon switching branches
When switching worktrees (which we can now do via the branch view) we re-layout the windows and their views.
We had the worktree view ahead of the file view based on the Flatten() method in context.go, because it used
to be associated with the branches panel.
2023-07-29 17:36:18 +10:00
Jesse Duffield
c9a19f0fbc Use forward-slashes on windows
We want to be using forward slashes everywhere internally, so if we get a path from windows
we should immediately convert it to use forward slashes.

I'm leaving out the recent repos list because that would require a migration
2023-07-29 17:36:18 +10:00
Jesse Duffield
6d48c0ead3 Add section in integration readme about testing against old git versions 2023-07-29 17:36:18 +10:00
Jesse Duffield
0e91a2f16e Add more i18n for worktrees 2023-07-29 17:36:18 +10:00
Jesse Duffield
693048eb29 Use fields rather than methods on worktrees
I would prefer to use methods to keep things immutable but I'd rather be consistent with the other
models and update them all at once
2023-07-29 17:36:18 +10:00
Jesse Duffield
873a68949e Centralise logic for obtaining repo paths
There are quite a few paths you might want to get e.g. the repo's path, the worktree's path,
the repo's git dir path, the worktree's git dir path. I want these all obtained once and
then used when needed rather than having to have IO whenever we need them. This is not so
much about reducing time spent on IO as it is about not having to care about errors every time
we want a path.
2023-07-29 17:36:18 +10:00
Jesse Duffield
f8c18d074a Remove IO logic from presentation code for worktrees
We're doing all the IO in our workers loader method so that we don't need to do any
in our presentation code
2023-07-29 17:36:18 +10:00
Jesse Duffield
9fb0fe1e35 Add test for opening lazygit in the worktree of a bare repo 2023-07-29 17:36:18 +10:00
Jesse Duffield
d599511c14 Remove dead function 2023-07-29 17:36:18 +10:00
Jesse Duffield
180908d244 Fix test by making branches appear deterministically
This fixes pkg/integration/tests/worktree/rebase.go which was failing on old git versions due to a difference in
order of branches that don't have recency values
2023-07-29 17:36:18 +10:00
Jesse Duffield
30cf274874 Allow entering a submodule by pressing space 2023-07-29 17:36:18 +10:00
Jesse Duffield
10287c0dc3 Allow entering a worktree by pressing enter 2023-07-29 17:36:18 +10:00
Jesse Duffield
939c573907 Update repo switch logic
We now always re-use the state of the repo if we're returning to it, and we always reset the windows to their default tabs.

We reset to default tabs because it's easy to implement. If people want to:
* have tab states be retained when switching
* have tab states specific to the current repo retained when switching back

Then we'll need to revisit this
2023-07-29 17:36:18 +10:00
Jesse Duffield
30d24d0954 Add test for retained context focus when switching worktrees 2023-07-29 17:36:18 +10:00
Jesse Duffield
4a2c21bc10 Support fastforwarding worktree 2023-07-29 17:36:18 +10:00
Jesse Duffield
df78ffd081 Add more worktree tests 2023-07-29 17:36:18 +10:00
Jesse Duffield
0eac6483ec Add worktree tests for removing/detaching 2023-07-29 17:36:18 +10:00
Jesse Duffield
71070827f2 Add worktree integration tests 2023-07-29 17:36:18 +10:00
Jesse Duffield
fb73bb7e25 Update cheatsheets 2023-07-29 17:36:18 +10:00
Jesse Duffield
e782b77475 Remove worktree version guards
Our min required git version is 2.20 so there's no need to add guards
for worktrees because they were added in 2.5
2023-07-29 17:36:18 +10:00
Jesse Duffield
d764f35c5d Fix unit tests 2023-07-29 17:36:18 +10:00
Jesse Duffield
b06f938e00 Show loader when switching worktrees 2023-07-29 17:36:18 +10:00
Jesse Duffield
ca0b966f44 Support older versions of git when fetching worktrees
Older versions of git don't support the -z flag in `git worktree list`.
So we're using newlines.

Also, we're not raising an error upon error because that triggers another refresh,
which gets us into an infinite loop
2023-07-29 17:36:18 +10:00
Jesse Duffield
514c2f0897 Fix tests
Going and fixing up some submodule tests which were broken by bad assumptions with worktree code
2023-07-29 17:36:18 +10:00
Jesse Duffield
cef0186b7d Fix tests
We now change directories to the repo on startup so we don't need to determine the test path in some special way
2023-07-29 17:36:18 +10:00
Jesse Duffield
af5ec473f3 Safer fetching of linked worktree paths 2023-07-29 17:36:18 +10:00
Jesse Duffield
faa3a612eb Move worktrees tab to files window 2023-07-29 17:36:18 +10:00
Jesse Duffield
47717802f8 Change directory to worktree if given as an argument
Previously we used an env var for this and it's not clear to me how that worked but
with this PR current directory = worktree directory
2023-07-29 17:36:18 +10:00
Jesse Duffield
b79d19e0a7 Support opening worktree in editor 2023-07-29 17:36:18 +10:00
Jesse Duffield
0cef35324f Properly render worktrees in files panel 2023-07-29 17:36:18 +10:00
Jesse Duffield
1638354474 Better logic for knowing which repo we're in 2023-07-29 17:36:18 +10:00
Jesse Duffield
96e41fa985 Only show worktree in status panel if not the main worktree and worktrees are supported 2023-07-29 17:36:18 +10:00
Jesse Duffield
4c1d706a85 Hide worktree functionality on old git versions 2023-07-29 17:36:18 +10:00
Jesse Duffield
b6d5542b35 Associate branches with worktrees even when mid-rebase 2023-07-29 17:36:18 +10:00
Jesse Duffield
72d7b91640 Assume that the base of a worktree can be checked out 2023-07-29 17:36:18 +10:00
Jesse Duffield
32ceeaa43f i18n for worktrees 2023-07-29 17:36:18 +10:00
Jesse Duffield
82da8d7586 Don't quit on error 2023-07-29 17:36:17 +10:00
Jesse Duffield
096b8a19c5 Allow opening worktree in editor
This does the job but I think we need yet another editor command for opening a directory in a new window.
2023-07-29 17:36:17 +10:00
Jesse Duffield
8f51c80d76 Show base ref suggestions when creating worktree 2023-07-29 17:36:17 +10:00
Jesse Duffield
33fc54d6e2 Refresh work trees when discarding file changes
We do this because we may be deleting a worktree folder so we'll need to show that in the worktrees view
2023-07-29 17:36:17 +10:00
Jesse Duffield
c8dcd002e2 Checkout worktree when creating from worktree view 2023-07-29 17:36:17 +10:00
Jesse Duffield
49b8806cc4 Use 'M' for months in branches panel 2023-07-29 17:36:17 +10:00
Jesse Duffield
e2cef55f94 Fix filtering logic in worktrees view 2023-07-29 17:36:17 +10:00
Jesse Duffield
500c0eafb9 Support creating worktrees from refs 2023-07-29 17:36:17 +10:00
Jesse Duffield
e0c8a56db5 Fix wording 2023-07-29 17:36:17 +10:00
Jesse Duffield
002e4eedf5 Log when directory is changed 2023-07-29 17:36:17 +10:00
Jesse Duffield
a991525391 Handle deleting branch attached to worktree 2023-07-29 17:36:17 +10:00
Jesse Duffield
7be3658533 Update wording 2023-07-29 17:36:17 +10:00
Jesse Duffield
fd854fb7f7 Don't touch repo stack when switching worktrees
We shouldn't touch this cos we're doing a lateral move
2023-07-29 17:36:17 +10:00
Jesse Duffield
64711dfc2f Move status panel presentation logic into presentation package 2023-07-29 17:36:17 +10:00
Jesse Duffield
0776ae41d3 Land in the same panel when switching to a worktree 2023-07-29 17:36:17 +10:00
Jesse Duffield
ad017f3bb8 Move current worktree to top of list 2023-07-29 17:36:17 +10:00
Jesse Duffield
9eb9ce88b3 Prompt to switch to worktree when branch is checked out by other worktree 2023-07-29 17:36:17 +10:00
Jesse Duffield
7af9715a1b Use git lingo 2023-07-29 17:36:17 +10:00
Jesse Duffield
dbd32f63dd Improve name handling 2023-07-29 17:36:17 +10:00
Jesse Duffield
d7aca1bcbd Use sentence case 2023-07-29 17:36:17 +10:00
Jesse Duffield
72c856ac13 Refactor 2023-07-29 17:36:17 +10:00
Jesse Duffield
038de926b3 Update worktree model 2023-07-29 17:36:17 +10:00
Jesse Duffield
4c471dff83 Alert when attempting to enter the current worktree 2023-07-29 17:36:17 +10:00
Jesse Duffield
c1ea17b66f Remove comment 2023-07-29 17:36:17 +10:00
Joel Baranick
b22f936522 Address PR comments 2023-07-29 17:36:17 +10:00
Joel Baranick
53e408f156 Basic support for adding a worktree 2023-07-29 17:36:17 +10:00
Joel Baranick
46bb9bb60b Put all worktree i18n strings together
Use tabwriter to align worktree panel contents
2023-07-29 17:36:17 +10:00
Joel Baranick
b10670ba44 Improve worktree panel 2023-07-29 17:36:17 +10:00
Joel Baranick
f13f85de8e Style missing worktree as red and display better error when trying to switch to them
Use a broken link icon for missing worktrees
2023-07-29 17:36:17 +10:00
Joel Baranick
141011aaae Hide worktrees in the worktree panel if they point at a non-existing filesystem location.
Remove unneeded check when filtering out branches from non-current worktrees from the branch panel.
Add link icon for linked worktrees
2023-07-29 17:36:17 +10:00
Joel Baranick
4a2c54e5d8 Update status to differentiate the main vs linked worktrees 2023-07-29 17:36:17 +10:00
Joel Baranick
2f156b3438 Support for deleting a worktree 2023-07-29 17:36:17 +10:00
Joel Baranick
eded08bacf Initial addition of support for worktrees 2023-07-29 17:36:17 +10:00
344 changed files with 4202 additions and 4132 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v1
with:

View File

@@ -1,7 +1,7 @@
name: Continuous Integration
env:
GO_VERSION: 1.20
GO_VERSION: 1.18
on:
push:
@@ -32,7 +32,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v3
with:
@@ -91,7 +91,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
@@ -117,7 +117,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
@@ -153,7 +153,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
@@ -187,7 +187,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:

3
.gitignore vendored
View File

@@ -40,5 +40,4 @@ test/results/**
oryxBuildBinary
__debug_bin
.worktrees
demo/output/*
.worktrees

View File

@@ -26,5 +26,4 @@ linters-settings:
max-func-lines: 0
run:
go: '1.20'
timeout: 10m
go: 1.18

View File

@@ -2,7 +2,7 @@
# docker build -t lazygit .
# docker run -it lazygit:latest /bin/sh
FROM golang:1.20 as build
FROM golang:1.18 as build
WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY go.mod go.sum ./
RUN go mod download

112
README.md

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
This directory contains stuff for recording lazygit demos.

View File

@@ -1,109 +0,0 @@
# Specify a command to be executed
# like `/bin/bash -l`, `ls`, or any other commands
# the default is bash for Linux
# or powershell.exe for Windows
command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG"
# Specify the current working directory path
# the default is the current working directory path
cwd: null
# Export additional ENV variables
env:
recording: true
# Explicitly set the number of columns
# or use `auto` to take the current
# number of columns of your shell
cols: 120 # 100
# Explicitly set the number of rows
# or use `auto` to take the current
# number of rows of your shell
rows: 35 # 30
# Amount of times to repeat GIF
# If value is -1, play once
# If value is 0, loop indefinitely
# If value is a positive number, loop n times
repeat: 0
# Quality
# 1 - 100
# Higher quality seems to make no difference, but running it through
# gifsicle ends up with a much better compressed version.
quality: 100
# Delay between frames in ms
# If the value is `auto` use the actual recording delays
frameDelay: auto
# Maximum delay between frames in ms
# Ignored if the `frameDelay` isn't set to `auto`
# Set to `auto` to prevent limiting the max idle time
maxIdleTime: 2000
# The surrounding frame box
# The `type` can be null, window, floating, or solid`
# To hide the title use the value null
# Don't forget to add a backgroundColor style with a null as type
frameBox:
type: floating
title: Lazygit
style:
border: 0px black solid
backgroundColor: "#1d1d1d"
margin: -5px
# Add a watermark image to the rendered gif
# You need to specify an absolute path for
# the image on your machine or a URL, and you can also
# add your own CSS styles
watermark:
imagePath: null
style:
position: absolute
right: 15px
bottom: 15px
width: 100px
opacity: 0.9
# Cursor style can be one of
# `block`, `underline`, or `bar`
cursorStyle: block
# Font family
# You can use any font that is installed on your machine
# in CSS-like syntax
fontFamily: "DejaVuSansMono Nerd Font"
# The size of the font
fontSize: 8
# The height of lines
lineHeight: 1
# The spacing between letters
letterSpacing: 0
# Theme
theme:
background: "transparent"
foreground: "#dddad6"
cursor: "#c7c7c7"
black: "#7a7a7a"
red: "#fc4384"
green: "#b3e33b"
yellow: "#ffa727"
blue: "#102895"
magenta: "#c930c7"
cyan: "#00c5c7"
white: "#c7c7c7"
brightBlack: "#676767"
brightRed: "#ff7fac"
brightGreen: "#c8ed71"
brightYellow: "#ebdf86"
brightBlue: "#6871ff"
brightMagenta: "#ff76ff"
brightCyan: "#5ffdff"
brightWhite: "#fffefe"

View File

@@ -1,81 +0,0 @@
#!/bin/sh
set -e
TYPE=$1
TEST=$2
usage() {
echo "Usage: $0 [gif|mp4] <test path>"
echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go"
exit 1
}
if [ "$#" -ne 2 ]
then
usage
fi
if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ]
then
usage
exit 1
fi
if [ -z "$TEST" ]
then
usage
fi
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
if [ -z "$WORKTREE_PATH" ]
then
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
echo "git worktree add .worktrees/assets assets"
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
exit 1
fi
OUTPUT_DIR="$WORKTREE_PATH/demo"
if ! command -v terminalizer &> /dev/null
then
echo "terminalizer could not be found"
echo "Install it with: npm install -g terminalizer"
exit 1
fi
if ! command -v "gifsicle" &> /dev/null
then
echo "gifsicle could not be found"
echo "Install it with: npm install -g gifsicle"
exit 1
fi
# Get last part of the test path and set that as the output name
# example test path: pkg/integration/tests/01_basic_test.go
# For that we want: NAME=01_basic_test
NAME=$(echo "$TEST" | sed -e 's/.*\///' | sed -e 's/\..*//')
# Add the demo to the tests list (if missing) so that it can be run
go generate pkg/integration/tests/tests.go
mkdir -p "$OUTPUT_DIR"
# First we record the demo into a yaml representation
terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME"
# Then we render it into a gif
terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif"
# Then we convert it to either an mp4 or gif based on the command line argument
if [ "$TYPE" = "mp4" ]
then
COMPRESSED_PATH="$OUTPUT_DIR/$NAME.mp4"
ffmpeg -y -i "$OUTPUT_DIR/$NAME.gif" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" "$COMPRESSED_PATH"
else
COMPRESSED_PATH="$OUTPUT_DIR/$NAME-compressed.gif"
gifsicle --colors 256 --use-col=web -O3 < "$OUTPUT_DIR/$NAME.gif" > "$COMPRESSED_PATH"
fi
echo "Demo recorded to $COMPRESSED_PATH"

View File

@@ -62,6 +62,7 @@ gui:
showListFooter: true # for seeing the '5 of 20' message in list panels
showRandomTip: true
showBranchCommitHash: false # show commit hashes alongside branch names
experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
showCommandLog: true
showIcons: false # deprecated: use nerdFontsVersion instead
@@ -70,7 +71,6 @@ gui:
splitDiff: 'auto' # one of 'auto' | 'always'
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
animateExplosion: true # shows an explosion animation when nuking the working tree
git:
paging:
colorArg: always
@@ -106,7 +106,6 @@ git:
parseEmoji: false
diffContextSize: 3 # how many lines of context are shown around a change in diffs
os:
copyToClipboardCmd: '' # See 'Custom Command for Copying to Clipboard' section
editPreset: '' # see 'Configuring File Editing' section
edit: ''
editAtLine: ''
@@ -279,20 +278,6 @@ os:
open: 'open {{filename}}'
```
### Custom Command for Copying to Clipboard
```yaml
os:
copyToClipboardCmd: ''
```
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
```
os:
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty
```
### Configuring File Editing
There are two commands for opening files, `o` for "open" and `e` for "edit". `o`

View File

@@ -6,5 +6,4 @@
* [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md)
* [Searching/Filtering](./Searching.md)
* [Stacked Branches](./Stacked_Branches.md)
* [Dev docs](./dev)

View File

@@ -1,18 +0,0 @@
# Working with stacked branches
When working on a large branch it can often be useful to break it down into
smaller pieces, and it can help to create separate branches for each independent
chunk of changes. For example, you could have one branch for preparatory
refactorings, one for backend changes, and one for frontend changes. Those
branches would then all be stacked onto each other.
Git has support for rebasing such a stack as a whole; you can enable it by
setting the git config `rebase.updateRfs` to true. If you then rebase the
topmost branch of the stack, the other ones in the stack will follow. This
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 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

@@ -1,82 +0,0 @@
# Demo Recordings
We want our demo recordings to be consistent and easy to update if we make changes to Lazygit's UI. Luckily for us, we have an existing recording system for the sake of our integration tests, so we can piggyback on that.
You'll want to familiarise yourself with how integration tests are written: see [here](../../pkg/integration/README.md).
## Prerequisites
Ideally we'd run this whole thing through docker but we haven't got that working. So you will need:
```
# for recording
npm i -g terminalizer
# for gif compression
npm i -g gifsicle
# for mp4 conversion
brew install ffmpeg
# font with icons
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \
tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \
rm DejaVuSansMono.tar.xz
```
## Creating a demo
Demos are found in `pkg/integration/tests/demo/`. They are like regular integration tests but have `IsDemo: true` which has a few effects:
* The bottom row of the UI is quieter so that we can render captions
* Fetch/Push/Pull have artificial latency to mimic a network request
* The loader at the bottom-right does not appear
In demos, we don't need to be as strict in our assertions as we are in tests. But it's still good to have some basic assertions so that if we automate the process of updating demos we'll know if one of them has broken.
You can use the same flow as we use with integration tests when you're writing a demo:
* Setup the repo
* Run the demo in sandbox mode to get a feel of what needs to happen
* Come back and write the code to make it happen
### Adding captions
It's good to add captions explaining what task if being performed. Use the existing demos as a guide.
### Setting up the assets worktree
We store assets (which includes demo recordings) in the `assets` branch, which is a branch that shares no history with the main branch and exists purely for storing assets. Storing them separately means we don't clog up the code branches with large binaries.
The scripts and demo definitions live in the code branches but the output lives in the assets branch so to be able to create a video from a demo you'll need to create a linked worktree for the assets branch which you can do with:
```sh
git worktree add .worktrees/assets assets
```
Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things:
* the yaml of the recording
* the original gif
* either the compressed gif or the mp4 depending on the output you chose (see below)
### Recording the demo
Once you're happy with your demo you can record it using:
```sh
scripts/record_demo.sh [gif|mp4] <path>
# e.g.
scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go
```
~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~
~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~
Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now.
### Including demos in README/docs
If you've followed the above steps you'll end up with your output in your assets worktree.
Within that worktree, stage all three output files and raise a PR against the assets branch.
Then back in the code branch, in the doc, you can embed the recording like so:
```md
![Nuke working tree](../assets/demo/interactive_rebase-compressed.gif)
```
This means we can update assets without needing to update the docs that embed them.

View File

@@ -2,4 +2,3 @@
* [Busy/Idle tracking](./Busy.md).
* [Integration Tests](../../pkg/integration/README.md)
* [Demo Recordings](./Demo_Recordings.md)

View File

@@ -84,7 +84,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: Move commit down one
<kbd>&lt;c-k&gt;</kbd>: Move commit up one
<kbd>v</kbd>: Paste commits (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Amend commit with staged changes
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: Revert commit

View File

@@ -103,7 +103,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: コミットを1つ下に移動
<kbd>&lt;c-k&gt;</kbd>: コミットを1つ上に移動
<kbd>v</kbd>: コミットを貼り付け (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: ステージされた変更でamendコミット
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: コミットをrevert

View File

@@ -265,7 +265,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: 커밋을 1개 아래로 이동
<kbd>&lt;c-k&gt;</kbd>: 커밋을 1개 위로 이동
<kbd>v</kbd>: 커밋을 붙여넣기 (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Amend commit with staged changes
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: 커밋 되돌리기

View File

@@ -143,7 +143,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: Verplaats commit 1 naar beneden
<kbd>&lt;c-k&gt;</kbd>: Verplaats commit 1 naar boven
<kbd>v</kbd>: Plak commits (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Wijzig commit met staged veranderingen
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: Commit ongedaan maken

View File

@@ -69,7 +69,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: Przenieś commit 1 w dół
<kbd>&lt;c-k&gt;</kbd>: Przenieś commit 1 w górę
<kbd>v</kbd>: Wklej commity (przebieranie)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Popraw commit zmianami z poczekalni
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: Odwróć commit

View File

@@ -151,7 +151,6 @@ _Связки клавиш_
<kbd>&lt;c-j&gt;</kbd>: Переместить коммит вниз на один
<kbd>&lt;c-k&gt;</kbd>: Переместить коммит вверх на один
<kbd>v</kbd>: Вставить отобранные коммиты (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Править последний коммит с проиндексированными изменениями
<kbd>a</kbd>: Установить/убрать автора коммита
<kbd>t</kbd>: Отменить коммит

View File

@@ -147,7 +147,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: 下移提交
<kbd>&lt;c-k&gt;</kbd>: 上移提交
<kbd>v</kbd>: 粘贴提交(拣选)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: 用已暂存的更改来修补提交
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: 还原提交

View File

@@ -191,7 +191,6 @@ _說明`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B`B`表示 Shift+B_
<kbd>&lt;c-j&gt;</kbd>: 向下移動提交
<kbd>&lt;c-k&gt;</kbd>: 向上移動提交
<kbd>v</kbd>: 貼上提交 (揀選)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: 使用已預存的更改修正提交
<kbd>a</kbd>: 設置/重設提交作者
<kbd>t</kbd>: 還原提交

20
go.mod
View File

@@ -1,21 +1,24 @@
module github.com/jesseduffield/lazygit
go 1.20
go 1.18
require (
github.com/OpenPeeDeeP/xdg v1.0.0
github.com/atotto/clipboard v0.1.4
github.com/aybabtme/humanlog v0.4.1
github.com/cli/safeexec v1.0.0
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/fsmiamoto/git-todo-parser v0.0.5
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell/v2 v2.6.0
github.com/go-errors/errors v1.4.2
github.com/gookit/color v1.4.2
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.20230806095321-ac7b03108825
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144
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
@@ -23,7 +26,7 @@ require (
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/kyokomi/emoji/v2 v2.2.8
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.15
github.com/mattn/go-runewidth v0.0.14
github.com/mgutz/str v1.2.0
github.com/pmezard/go-difflib v1.0.0
github.com/sahilm/fuzzy v0.1.0
@@ -31,12 +34,9 @@ require (
github.com/sanity-io/litter v1.5.2
github.com/sasha-s/go-deadlock v0.3.1
github.com/sirupsen/logrus v1.4.2
github.com/spf13/afero v1.9.5
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68
github.com/stretchr/testify v1.8.0
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -64,11 +64,13 @@ require (
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

34
go.sum
View File

@@ -55,6 +55,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
@@ -82,9 +84,13 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsmiamoto/git-todo-parser v0.0.5 h1:Bhzd/vz/6Qm3udfkd6NO9fWfD3TpwR9ucp3N75/J5I8=
github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
@@ -179,8 +185,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.20230806095321-ac7b03108825 h1:4Ea8qV/BbZAGcXd8MAufDsbwwfz2pbRZdqIodC/XHZs=
github.com/jesseduffield/gocui v0.3.1-0.20230806095321-ac7b03108825/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4=
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144 h1:gwy5JzP6+PhcPFG1obkUSLGcTkUY88sLKlCPOFjwtak=
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
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=
@@ -212,6 +218,7 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE=
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -223,9 +230,9 @@ 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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/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=
@@ -239,14 +246,15 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
@@ -268,8 +276,6 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68 h1:NSTj9xAKUu85d6pAdNFyblL84BfiOB1rVnzxQO/cYUk=
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68/go.mod h1:PuJ7T6QKbsU7iVOHlhRoV3D/ipIAJsyiV4dbwcVaYj8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
@@ -308,6 +314,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -451,13 +458,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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/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.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
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=
@@ -467,8 +475,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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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=

View File

@@ -13,6 +13,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/jesseduffield/generics/slices"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -266,11 +267,7 @@ func (app *App) Run(startArgs appTypes.StartArgs) error {
// Close closes any resources
func (app *App) Close() error {
for _, closer := range app.closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
return slices.TryForEach(app.closers, func(closer io.Closer) error {
return closer.Close()
})
}

View File

@@ -5,12 +5,12 @@ import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -92,7 +92,7 @@ func getDaemonKind() DaemonKind {
}
func getCommentChar() byte {
cmd := exec.Command("git", "config", "--get", "--null", "core.commentChar")
cmd := secureexec.Command("git", "config", "--get", "--null", "core.commentChar")
if output, err := cmd.Output(); err == nil && len(output) == 2 {
return output[0]
}

View File

@@ -6,10 +6,10 @@ import (
"strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/samber/lo"
)
type TodoLine struct {
@@ -26,11 +26,11 @@ func (self *TodoLine) ToString() string {
}
func TodoLinesToString(todoLines []TodoLine) string {
lines := lo.Map(todoLines, func(todoLine TodoLine, _ int) string {
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
return todoLine.ToString()
})
return strings.Join(lo.Reverse(lines), "")
return strings.Join(slices.Reverse(lines), "")
}
type ChangeTodoAction struct {

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
@@ -18,6 +17,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/env"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs/tail"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3"
@@ -280,7 +280,7 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
}
func getGitVersionInfo() string {
cmd := exec.Command("git", "--version")
cmd := secureexec.Command("git", "--version")
stdout, _ := cmd.Output()
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
return gitVersion

View File

@@ -3,8 +3,8 @@ package app
import (
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo"
)
type errorMapping struct {
@@ -18,7 +18,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
knownErrorMessages := []string{tr.MinGitVersionError}
if lo.Contains(knownErrorMessages, errorMessage) {
if slices.Contains(knownErrorMessages, errorMessage) {
return errorMessage, true
}
@@ -29,7 +29,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
},
}
if mapping, ok := lo.Find(mappings, func(mapping errorMapping) bool {
if mapping, ok := slices.Find(mappings, func(mapping errorMapping) bool {
return strings.Contains(errorMessage, mapping.originalError)
}); ok {
return mapping.newError, true

View File

@@ -15,6 +15,7 @@ import (
"strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -22,7 +23,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
type bindingSection struct {
@@ -129,7 +129,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
bindingsToDisplay := lo.Filter(bindings, func(binding *types.Binding, _ int) bool {
bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool {
if lo.Contains(excludedViews, binding.ViewName) {
return false
}
@@ -162,7 +162,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
return a.header.title < b.header.title
})
return lo.Map(bindingGroups, func(hb headerWithBindings, _ int) *bindingSection {
return slices.Map(bindingGroups, func(hb headerWithBindings) *bindingSection {
return &bindingSection{
title: hb.header.title,
bindings: hb.bindings,

View File

@@ -125,15 +125,6 @@ func (self *BisectCommands) Start() error {
return self.cmd.New(cmdArgs).StreamOutput().Run()
}
func (self *BisectCommands) StartWithTerms(oldTerm string, newTerm string) error {
cmdArgs := NewGitCmd("bisect").Arg("start").
Arg("--term-old=" + oldTerm).
Arg("--term-new=" + newTerm).
ToArgv()
return self.cmd.New(cmdArgs).StreamOutput().Run()
}
// tells us whether we've found our problem commit(s). We return a string slice of
// commit sha's if we're done, and that slice may have more that one item if
// skipped commits are involved.

View File

@@ -2,7 +2,7 @@ package git_commands
import (
"github.com/jesseduffield/generics/maps"
"github.com/samber/lo"
"github.com/jesseduffield/generics/slices"
"github.com/sirupsen/logrus"
)
@@ -97,5 +97,5 @@ func (self *BisectInfo) Bisecting() bool {
return false
}
return lo.Contains(maps.Values(self.statusMap), BisectStatusOld)
return slices.Contains(maps.Values(self.statusMap), BisectStatusOld)
}

View File

@@ -70,21 +70,6 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
}, nil
}
// CurrentBranchName get name of current branch
func (self *BranchCommands) CurrentBranchName() (string, error) {
cmdArgs := NewGitCmd("rev-parse").
Arg("--abbrev-ref").
Arg("--verify").
Arg("HEAD").
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err == nil {
return strings.TrimSpace(output), nil
}
return "", err
}
// Delete delete branch
func (self *BranchCommands) Delete(branch string, force bool) error {
cmdArgs := NewGitCmd("branch").

View File

@@ -6,13 +6,13 @@ import (
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
// context:
@@ -75,7 +75,7 @@ outer:
if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch)
branches = utils.Remove(branches, j)
branches = slices.Remove(branches, j)
continue outer
}
}
@@ -87,14 +87,14 @@ outer:
return a.Name < b.Name
})
branches = utils.Prepend(branches, branchesWithRecency...)
branches = slices.Prepend(branches, branchesWithRecency...)
foundHead := false
for i, branch := range branches {
if branch.Head {
foundHead = true
branch.Recency = " *"
branches = utils.Move(branches, i, 0)
branches = slices.Move(branches, i, 0)
break
}
}
@@ -103,7 +103,7 @@ outer:
if err != nil {
return nil, err
}
branches = utils.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
}
configBranches, err := self.config.Branches()
@@ -131,7 +131,7 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
trimmedOutput := strings.TrimSpace(output)
outputLines := strings.Split(trimmedOutput, "\n")
return lo.FilterMap(outputLines, func(line string, _ int) (*models.Branch, bool) {
return slices.FilterMap(outputLines, func(line string) (*models.Branch, bool) {
if line == "" {
return nil, false
}
@@ -171,7 +171,7 @@ var branchFields = []string{
"upstream:short",
"upstream:track",
"subject",
"objectname",
fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
}
// Obtain branch information from parsed line output of getRawBranches()

View File

@@ -3,6 +3,7 @@ package git_commands
import (
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
@@ -51,7 +52,7 @@ func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
}
// typical result looks like 'A my_file' meaning my_file was added
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile {
return slices.Map(lo.Chunk(lines, 2), func(chunk []string) *models.CommitFile {
return &models.CommitFile{
ChangeStatus: chunk[0],
Name: chunk[1],

View File

@@ -11,6 +11,7 @@ import (
"sync"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@@ -135,7 +136,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
}
if ancestor != "" {
commits = setCommitMergedStatuses(ancestor, commits)
commits = self.setCommitMergedStatuses(ancestor, commits)
}
return commits, nil
@@ -232,7 +233,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
return nil, nil
}
commitShas := lo.FilterMap(commits, func(commit *models.Commit, _ int) (string, bool) {
commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
return commit.Sha, commit.Sha != ""
})
@@ -378,7 +379,7 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
// Command does not have a commit associated, skip
continue
}
commits = utils.Prepend(commits, &models.Commit{
commits = slices.Prepend(commits, &models.Commit{
Sha: t.Commit,
Name: t.Msg,
Status: models.StatusRebasing,
@@ -492,11 +493,10 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
}
}
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*models.Commit {
func (self *CommitLoader) setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*models.Commit {
passedAncestor := false
for i, commit := range commits {
// some commits aren't really commits and don't have sha's, such as the update-ref todo
if commit.Sha != "" && strings.HasPrefix(ancestor, commit.Sha) {
if strings.HasPrefix(ancestor, commit.Sha) {
passedAncestor = true
}
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {

View File

@@ -506,50 +506,3 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
})
}
}
func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
type scenario struct {
testName string
commits []*models.Commit
ancestor string
expectedCommits []*models.Commit
}
scenarios := []scenario{
{
testName: "basic",
commits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusPushed},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
ancestor: "67890",
expectedCommits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusMerged},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusMerged},
},
},
{
testName: "with update-ref",
commits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
ancestor: "deadbeef",
expectedCommits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.testName, func(t *testing.T) {
expectedCommits := setCommitMergedStatuses(scenario.ancestor, scenario.commits)
assert.Equal(t, scenario.expectedCommits, expectedCommits)
})
}
}

View File

@@ -107,7 +107,3 @@ func (self *ConfigCommands) GetCoreCommentChar() byte {
return '#'
}
func (self *ConfigCommands) GetRebaseUpdateRefs() bool {
return self.gitConfig.GetBool("rebase.updateRefs")
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/app/daemon"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -104,13 +105,7 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int)
sha := commits[index].Sha
msg := utils.ResolvePlaceholderString(
self.Tr.Log.MoveCommitDown,
map[string]string{
"shortSha": utils.ShortSha(sha),
},
)
self.os.LogCommand(msg, false)
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
@@ -124,13 +119,7 @@ func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) er
sha := commits[index].Sha
msg := utils.ResolvePlaceholderString(
self.Tr.Log.MoveCommitUp,
map[string]string{
"shortSha": utils.ShortSha(sha),
},
)
self.os.LogCommand(msg, false)
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
@@ -161,37 +150,15 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in
}
func (self *RebaseCommands) EditRebase(branchRef string) error {
msg := utils.ResolvePlaceholderString(
self.Tr.Log.EditRebase,
map[string]string{
"ref": branchRef,
},
)
self.os.LogCommand(msg, false)
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: branchRef,
instruction: daemon.NewInsertBreakInstruction(),
}).Run()
}
func (self *RebaseCommands) EditRebaseFromBaseCommit(targetBranchName string, baseCommit string) error {
msg := utils.ResolvePlaceholderString(
self.Tr.Log.EditRebaseFromBaseCommit,
map[string]string{
"baseCommit": baseCommit,
"targetBranchName": targetBranchName,
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseCommit,
onto: targetBranchName,
instruction: daemon.NewInsertBreakInstruction(),
}).Run()
}
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
changeTodoStr := strings.Join(lo.Map(changes, func(c daemon.ChangeTodoAction, _ int) string {
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
}), "\n")
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
@@ -199,7 +166,6 @@ func logTodoChanges(changes []daemon.ChangeTodoAction) string {
type PrepareInteractiveRebaseCommandOpts struct {
baseShaOrRoot string
onto string
instruction daemon.Instruction
overrideEditor bool
keepCommitsThatBecomeEmpty bool
@@ -218,7 +184,6 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
ArgIf(opts.keepCommitsThatBecomeEmpty && !self.version.IsOlderThan(2, 26, 0), "--empty=keep").
Arg("--no-autosquash").
ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
ArgIf(opts.onto != "", "--onto", opts.onto).
Arg(opts.baseShaOrRoot).
ToArgv()
@@ -342,13 +307,6 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
}
func (self *RebaseCommands) RebaseBranchFromBaseCommit(targetBranchName string, baseCommit string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseCommit,
onto: targetBranchName,
}).Run()
}
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
cmdArgs := NewGitCmd(commandType).Arg("--" + command).ToArgv()
@@ -437,13 +395,7 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
})
msg := utils.ResolvePlaceholderString(
self.Tr.Log.CherryPickCommits,
map[string]string{
"commitLines": strings.Join(commitLines, "\n"),
},
)
self.os.LogCommand(msg, false)
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: "HEAD",

View File

@@ -4,13 +4,12 @@ import (
"strings"
"sync"
"github.com/jesseduffield/generics/slices"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
type RemoteLoader struct {
@@ -54,7 +53,7 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
return nil, remoteBranchesErr
}
remotes := lo.Map(goGitRemotes, func(goGitRemote *gogit.Remote, _ int) *models.Remote {
remotes := slices.Map(goGitRemotes, func(goGitRemote *gogit.Remote) *models.Remote {
remoteName := goGitRemote.Config().Name
branches := remoteBranchesByRemoteName[remoteName]

View File

@@ -5,11 +5,11 @@ import (
"strconv"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type StashLoader struct {
@@ -69,7 +69,7 @@ func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%gs").ToArgv()
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index)
})
}

View File

@@ -1,7 +1,6 @@
package git_commands
import (
"os"
"path/filepath"
"strconv"
"strings"
@@ -72,14 +71,3 @@ func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
func (self *StatusCommands) IsInMergeState() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
}
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
// being rebased, or empty string when we're not in a rebase
func (self *StatusCommands) BranchBeingRebased() string {
for _, dir := range []string{"rebase-merge", "rebase-apply"} {
if bytesContent, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), dir, "head-name")); err == nil {
return strings.TrimSpace(string(bytesContent))
}
}
return ""
}

View File

@@ -12,19 +12,16 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
}
}
func (self *TagCommands) CreateLightweight(tagName string, ref string, force bool) error {
cmdArgs := NewGitCmd("tag").
ArgIf(force, "--force").
Arg("--", tagName).
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
cmdArgs := NewGitCmd("tag").Arg("--", tagName).
ArgIf(len(ref) > 0, ref).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) error {
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
cmdArgs := NewGitCmd("tag").Arg(tagName).
ArgIf(force, "--force").
ArgIf(len(ref) > 0, ref).
Arg("-m", msg).
ToArgv()
@@ -32,15 +29,6 @@ func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) e
return self.cmd.New(cmdArgs).Run()
}
func (self *TagCommands) HasTag(tagName string) bool {
cmdArgs := NewGitCmd("show-ref").
Arg("--tags", "--quiet", "--verify", "--").
Arg("refs/tags/" + tagName).
ToArgv()
return self.cmd.New(cmdArgs).Run() == nil
}
func (self *TagCommands) Delete(tagName string) error {
cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
ToArgv()

View File

@@ -3,11 +3,11 @@ package git_commands
import (
"regexp"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type TagLoader struct {
@@ -38,7 +38,7 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
tags := lo.Map(split, func(line string, _ int) *models.Tag {
tags := slices.Map(split, func(line string) *models.Tag {
matches := lineRegex.FindStringSubmatch(line)
tagName := matches[1]
message := ""

View File

@@ -7,6 +7,8 @@ import (
"os/exec"
"strings"
"syscall"
"github.com/jesseduffield/lazygit/pkg/secureexec"
)
// including license from https://github.com/tcnksm/go-gitconfig because this file is an adaptation of that repo's code
@@ -53,10 +55,10 @@ func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
func getGitConfigCmd(key string) *exec.Cmd {
gitArgs := []string{"config", "--get", "--null", key}
return exec.Command("git", gitArgs...)
return secureexec.Command("git", gitArgs...)
}
func getGitConfigGeneralCmd(args string) *exec.Cmd {
gitArgs := append([]string{"config"}, strings.Split(args, " ")...)
return exec.Command("git", gitArgs...)
return secureexec.Command("git", gitArgs...)
}

View File

@@ -8,10 +8,9 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
"github.com/jesseduffield/generics/slices"
)
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc.
@@ -112,7 +111,7 @@ func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
serviceDefinition, ok := serviceDefinitionByProvider[provider]
if !ok {
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
providerNames := slices.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition) string {
return serviceDefinition.provider
})

View File

@@ -73,6 +73,10 @@ type ICmdObj interface {
}
type CmdObj struct {
// the secureexec package will swap out the first arg with the full path to the binary,
// so we store these args separately so that ToString() will output the original
args []string
cmd *exec.Cmd
runner ICmdObjRunner
@@ -117,7 +121,7 @@ func (self *CmdObj) GetCmd() *exec.Cmd {
func (self *CmdObj) ToString() string {
// if a given arg contains a space, we need to wrap it in quotes
quotedArgs := lo.Map(self.cmd.Args, func(arg string, _ int) string {
quotedArgs := lo.Map(self.args, func(arg string, _ int) string {
if strings.Contains(arg, " ") {
return `"` + arg + `"`
}
@@ -128,7 +132,7 @@ func (self *CmdObj) ToString() string {
}
func (self *CmdObj) Args() []string {
return self.cmd.Args
return self.args
}
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {

View File

@@ -3,9 +3,9 @@ package oscommands
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/mgutz/str"
)
@@ -27,10 +27,11 @@ type CmdObjBuilder struct {
var _ ICmdObjBuilder = &CmdObjBuilder{}
func (self *CmdObjBuilder) New(args []string) ICmdObj {
cmd := exec.Command(args[0], args[1:]...)
cmd := secureexec.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
args: args,
cmd: cmd,
runner: self.runner,
}

View File

@@ -27,8 +27,7 @@ func TestCmdObjToString(t *testing.T) {
}
for _, scenario := range scenarios {
cmd := exec.Command(scenario.cmdArgs[0], scenario.cmdArgs[1:]...)
cmdObj := &CmdObj{cmd: cmd}
cmdObj := &CmdObj{args: scenario.cmdArgs}
actual := cmdObj.ToString()
if actual != scenario.expected {
t.Errorf("Expected %s, got %s", quote(scenario.expected), quote(actual))

View File

@@ -3,6 +3,7 @@ package oscommands
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
@@ -105,7 +106,7 @@ func CopyDir(src string, dst string) (err error) {
return //nolint: nakedret
}
entries, err := os.ReadDir(src)
entries, err := ioutil.ReadDir(src)
if err != nil {
return //nolint: nakedret
}
@@ -120,14 +121,8 @@ func CopyDir(src string, dst string) (err error) {
return //nolint: nakedret
}
} else {
var info os.FileInfo
info, err = entry.Info()
if err != nil {
return //nolint: nakedret
}
// Skip symlinks.
if info.Mode()&os.ModeSymlink != 0 {
if entry.Mode()&os.ModeSymlink != 0 {
continue
}

View File

@@ -3,6 +3,8 @@ package oscommands
import (
"bufio"
"fmt"
"regexp"
"runtime"
"strings"
"sync"
"testing"
@@ -122,7 +124,27 @@ func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmd
func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
description := fmt.Sprintf("matches args %s", strings.Join(expectedArgs, " "))
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args)
args := cmdObj.GetCmd().Args
if runtime.GOOS == "windows" {
// thanks to the secureexec package, the first arg is something like
// '"C:\\Program Files\\Git\\mingw64\\bin\\<command>.exe"
// on windows so we'll just ensure it contains our program
if !strings.Contains(args[0], expectedArgs[0]) {
return false
}
} else {
// first arg is the program name
if expectedArgs[0] != args[0] {
return false
}
}
if !slices.Equal(expectedArgs[1:], args[1:]) {
return false
}
return true
}, output, err)
return self
@@ -131,7 +153,18 @@ func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, e
func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
description := fmt.Sprintf("matches git args %s", strings.Join(expectedArgs, " "))
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args[1:])
// first arg is 'git' on unix and something like '"C:\\Program Files\\Git\\mingw64\\bin\\git.exe" on windows so we'll just ensure it ends in either 'git' or 'git.exe'
re := regexp.MustCompile(`git(\.exe)?$`)
args := cmdObj.GetCmd().Args
if !re.MatchString(args[0]) {
return false
}
if !slices.Equal(expectedArgs, args[1:]) {
return false
}
return true
}, output, err)
return self

View File

@@ -13,6 +13,7 @@ import (
"github.com/samber/lo"
"github.com/atotto/clipboard"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/kill"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -117,15 +118,7 @@ func (c *OSCommand) Quote(message string) string {
// AppendLineToFile adds a new line in file
func (c *OSCommand) AppendLineToFile(filename, line string) error {
msg := utils.ResolvePlaceholderString(
c.Tr.Log.AppendingLineToFile,
map[string]string{
"line": line,
"filename": filename,
},
)
c.LogCommand(msg, false)
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return utils.WrapError(err)
@@ -162,13 +155,7 @@ func (c *OSCommand) AppendLineToFile(filename, line string) error {
// CreateFileWithContent creates a file with the given content
func (c *OSCommand) CreateFileWithContent(path string, content string) error {
msg := utils.ResolvePlaceholderString(
c.Tr.Log.CreateFileWithContent,
map[string]string{
"path": path,
},
)
c.LogCommand(msg, false)
c.LogCommand(fmt.Sprintf("Creating file '%s'", path), false)
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
c.Log.Error(err)
return err
@@ -184,13 +171,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
// Remove removes a file or directory at the specified path
func (c *OSCommand) Remove(filename string) error {
msg := utils.ResolvePlaceholderString(
c.Tr.Log.Remove,
map[string]string{
"filename": filename,
},
)
c.LogCommand(msg, false)
c.LogCommand(fmt.Sprintf("Removing '%s'", filename), false)
err := os.RemoveAll(filename)
return utils.WrapError(err)
}
@@ -208,7 +189,7 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
cmds := lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) *exec.Cmd {
cmds := slices.Map(cmdObjs, func(cmdObj ICmdObj) *exec.Cmd {
return cmdObj.GetCmd()
})
@@ -285,32 +266,12 @@ func PrepareForChildren(cmd *exec.Cmd) {
func (c *OSCommand) CopyToClipboard(str string) error {
escaped := strings.Replace(str, "\n", "\\n", -1)
truncated := utils.TruncateWithEllipsis(escaped, 40)
msg := utils.ResolvePlaceholderString(
c.Tr.Log.CopyToClipboard,
map[string]string{
"str": truncated,
},
)
c.LogCommand(msg, false)
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()
}
c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false)
return clipboard.WriteAll(str)
}
func (c *OSCommand) RemoveFile(path string) error {
msg := utils.ResolvePlaceholderString(
c.Tr.Log.RemoveFile,
map[string]string{
"path": path,
},
)
c.LogCommand(msg, false)
c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false)
return c.removeFileFn(path)
}

View File

@@ -6,6 +6,7 @@ package oscommands
import (
"testing"
"github.com/cli/safeexec"
"github.com/go-errors/errors"
"github.com/stretchr/testify/assert"
)
@@ -19,11 +20,13 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
test func(error)
}
fullCmdPath, _ := safeexec.LookPath("cmd")
scenarios := []scenario{
{
filename: "test",
runner: NewFakeRunner(t).
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", errors.New("error")),
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", errors.New("error")),
test: func(err error) {
assert.Error(t, err)
},
@@ -31,7 +34,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "test",
runner: NewFakeRunner(t).
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", nil),
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
@@ -39,7 +42,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "filename with spaces",
runner: NewFakeRunner(t).
ExpectArgs([]string{"cmd", "/c", "start", "", "filename with spaces"}, "", nil),
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "filename with spaces"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
@@ -47,7 +50,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "let's_test_with_single_quote",
runner: NewFakeRunner(t).
ExpectArgs([]string{"cmd", "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
@@ -55,7 +58,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "$USER.txt",
runner: NewFakeRunner(t).
ExpectArgs([]string{"cmd", "/c", "start", "", "$USER.txt"}, "", nil),
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "$USER.txt"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
)
@@ -208,10 +209,10 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
filenames := maps.Keys(p.fileInfoMap)
sort.Strings(filenames)
patches := lo.Map(filenames, func(filename string, _ int) string {
patches := slices.Map(filenames, func(filename string) string {
return p.RenderPatchForFile(filename, plain, false)
})
output := lo.Filter(patches, func(patch string, _ int) bool {
output := slices.Filter(patches, func(patch string) bool {
return patch != ""
})

View File

@@ -318,9 +318,8 @@ type AppState struct {
StartupPopupVersion int
// these are for custom commands typed in directly, not for custom commands in the lazygit config
CustomCommandsHistory []string
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
CustomCommandsHistory []string
HideCommandLog bool
}
func getDefaultAppState() *AppState {

View File

@@ -1,17 +1,18 @@
package config
import (
"io/ioutil"
"os"
"strings"
)
func isWSL() bool {
data, err := os.ReadFile("/proc/sys/kernel/osrelease")
data, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
return err == nil && strings.Contains(string(data), "microsoft")
}
func isContainer() bool {
data, err := os.ReadFile("/proc/1/cgroup")
data, err := ioutil.ReadFile("/proc/1/cgroup")
if strings.Contains(string(data), "docker") ||
strings.Contains(string(data), "/lxc/") ||

View File

@@ -27,36 +27,36 @@ type RefresherConfig struct {
}
type GuiConfig struct {
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
ShortTimeFormat string `yaml:"shortTimeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
NerdFontsVersion string `yaml:"nerdFontsVersion"`
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
AnimateExplosion bool `yaml:"animateExplosion"`
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
ShortTimeFormat string `yaml:"shortTimeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
NerdFontsVersion string `yaml:"nerdFontsVersion"`
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
}
type ThemeConfig struct {
@@ -68,8 +68,6 @@ type ThemeConfig struct {
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
DefaultFgColor []string `yaml:"defaultFgColor"`
}
@@ -270,7 +268,6 @@ type KeybindingCommitsConfig struct {
CherryPickCopy string `yaml:"cherryPickCopy"`
CherryPickCopyRange string `yaml:"cherryPickCopyRange"`
PasteCommits string `yaml:"pasteCommits"`
MarkCommitAsBaseForRebase string `yaml:"markCommitAsBaseForRebase"`
CreateTag string `yaml:"tagCommit"`
CheckoutCommit string `yaml:"checkoutCommit"`
ResetCherryPick string `yaml:"resetCherryPick"`
@@ -357,9 +354,6 @@ type OSConfig struct {
// OpenLinkCommand is the command for opening a link
// Deprecated: use OpenLink instead.
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
// CopyToClipboardCmd is the command for copying to clipboard
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
}
type CustomCommandAfterHook struct {
@@ -436,26 +430,24 @@ func GetDefaultConfig() *UserConfig {
SelectedRangeBgColor: []string{"blue"},
CherryPickedCommitBgColor: []string{"cyan"},
CherryPickedCommitFgColor: []string{"blue"},
MarkedBaseCommitBgColor: []string{"yellow"},
MarkedBaseCommitFgColor: []string{"blue"},
UnstagedChangesColor: []string{"red"},
DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ShowBranchCommitHash: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
AnimateExplosion: true,
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ExperimentalShowBranchHeads: false,
ShowBranchCommitHash: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
},
Git: GitConfig{
Paging: PagingConfig{
@@ -620,7 +612,6 @@ func GetDefaultConfig() *UserConfig {
CherryPickCopy: "c",
CherryPickCopyRange: "C",
PasteCommits: "v",
MarkCommitAsBaseForRebase: "B",
CreateTag: "T",
CheckoutCommit: "<space>",
ResetCherryPick: "<c-R>",

View File

@@ -4,9 +4,9 @@ import (
"errors"
"sync"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -134,7 +134,7 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, self.ContextStack = utils.Pop(self.ContextStack)
_, self.ContextStack = slices.Pop(self.ContextStack)
}
self.ContextStack = append(self.ContextStack, c)
@@ -154,7 +154,7 @@ func (self *ContextMgr) Pop() error {
}
var currentContext types.Context
currentContext, self.ContextStack = utils.Pop(self.ContextStack)
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
newContext := self.ContextStack[len(self.ContextStack)-1]

View File

@@ -83,7 +83,3 @@ func (self *BranchesContext) GetDiffTerminals() []string {
}
return nil
}
func (self *BranchesContext) ShowBranchHeadsInSubCommits() bool {
return true
}

View File

@@ -1,12 +1,12 @@
package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type CommitFilesContext struct {
@@ -34,7 +34,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
}
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
return lo.Map(lines, func(line string, _ int) []string {
return slices.Map(lines, func(line string) []string {
return []string{line}
})
}

View File

@@ -38,18 +38,13 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
}
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == enums.REBASE_MODE_REBASING
showBranchMarkerForHeadCommit := c.Git().Config.GetRebaseUpdateRefs()
return presentation.GetCommitListDisplayStrings(
c.Common,
c.Model().Commits,
c.Model().Branches,
c.Model().CheckedOutBranch,
showBranchMarkerForHeadCommit,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedShaSet(),
c.Modes().Diffing.Ref,
c.Modes().MarkedBaseCommit.GetSha(),
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
time.Now(),

View File

@@ -1,6 +1,7 @@
package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -81,11 +82,11 @@ func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment
// TODO: move into presentation package
func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]string {
menuItems := self.FilteredListViewModel.GetItems()
showKeys := lo.SomeBy(menuItems, func(item *types.MenuItem) bool {
showKeys := slices.Some(menuItems, func(item *types.MenuItem) bool {
return item.Key != nil
})
return lo.Map(menuItems, func(item *types.MenuItem, _ int) []string {
return slices.Map(menuItems, func(item *types.MenuItem) []string {
displayStrings := item.LabelColumns
if !showKeys {
@@ -106,18 +107,18 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
keyStyle = style.FgDefault.SetStrikethrough()
}
displayStrings = utils.Prepend(displayStrings, keyStyle.Sprint(keyLabel))
displayStrings = slices.Prepend(displayStrings, keyStyle.Sprint(keyLabel))
return displayStrings
})
}
func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
basicBindings := self.ListContextTrait.GetKeybindings(opts)
menuItemsWithKeys := lo.Filter(self.menuItems, func(item *types.MenuItem, _ int) bool {
menuItemsWithKeys := slices.Filter(self.menuItems, func(item *types.MenuItem) bool {
return item.Key != nil
})
menuItemBindings := lo.Map(menuItemsWithKeys, func(item *types.MenuItem, _ int) *types.Binding {
menuItemBindings := slices.Map(menuItemsWithKeys, func(item *types.MenuItem) *types.Binding {
return &types.Binding{
Key: item.Key,
Handler: func() error { return self.OnMenuPress(item) },

View File

@@ -86,7 +86,3 @@ func (self *ReflogCommitsContext) GetDiffTerminals() []string {
return []string{itemId}
}
func (self *ReflogCommitsContext) ShowBranchHeadsInSubCommits() bool {
return false
}

View File

@@ -72,7 +72,3 @@ func (self *RemoteBranchesContext) GetDiffTerminals() []string {
return []string{itemId}
}
func (self *RemoteBranchesContext) ShowBranchHeadsInSubCommits() bool {
return true
}

View File

@@ -37,13 +37,6 @@ func NewSubCommitsContext(
}
getDisplayStrings := func(startIdx int, length int) [][]string {
// This can happen if a sub-commits view is asked to be rerendered while
// it is invisble; for example when switching screen modes, which
// rerenders all views.
if viewModel.GetRef() == nil {
return [][]string{}
}
selectedCommitSha := ""
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
@@ -51,21 +44,12 @@ func NewSubCommitsContext(
selectedCommitSha = selectedCommit.Sha
}
}
branches := []*models.Branch{}
if viewModel.GetShowBranchHeads() {
branches = c.Model().Branches
}
showBranchMarkerForHeadCommit := c.Git().Config.GetRebaseUpdateRefs()
return presentation.GetCommitListDisplayStrings(
c.Common,
c.Model().SubCommits,
branches,
viewModel.GetRef().RefName(),
showBranchMarkerForHeadCommit,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedShaSet(),
c.Modes().Diffing.Ref,
"",
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
time.Now(),
@@ -113,8 +97,7 @@ type SubCommitsViewModel struct {
ref types.Ref
*ListViewModel[*models.Commit]
limitCommits bool
showBranchHeads bool
limitCommits bool
}
func (self *SubCommitsViewModel) SetRef(ref types.Ref) {
@@ -125,14 +108,6 @@ func (self *SubCommitsViewModel) GetRef() types.Ref {
return self.ref
}
func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) {
self.showBranchHeads = value
}
func (self *SubCommitsViewModel) GetShowBranchHeads() bool {
return self.showBranchHeads
}
func (self *SubCommitsContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {

View File

@@ -69,7 +69,3 @@ func (self *TagsContext) GetDiffTerminals() []string {
return []string{itemId}
}
func (self *TagsContext) ShowBranchHeadsInSubCommits() bool {
return true
}

View File

@@ -1,11 +1,11 @@
package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type WorkingTreeContext struct {
@@ -25,7 +25,7 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
getDisplayStrings := func(startIdx int, length int) [][]string {
lines := presentation.RenderFileTree(viewModel, c.Modes().Diffing.Ref, c.Model().Submodules)
return lo.Map(lines, func(line string, _ int) []string {
return slices.Map(lines, func(line string) []string {
return []string{line}
})
}

View File

@@ -56,6 +56,7 @@ func (gui *Gui) resetHelpersAndControllers() {
stagingHelper,
mergeConflictsHelper,
worktreeHelper,
gui.fileWatcher,
)
diffHelper := helpers.NewDiffHelper(helperCommon)
cherryPickHelper := helpers.NewCherryPickHelper(

View File

@@ -8,8 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type BisectController struct {
@@ -67,18 +65,12 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
// ref, because we'll be reloading our commits in that case.
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
// If we have a current sha already, then we always want to use that one. If
// not, we're still picking the initial commits before we really start, so
// use the selected commit in that case.
shaToMark := lo.Ternary(info.GetCurrentSha() != "", info.GetCurrentSha(), commit.Sha)
shortShaToMark := utils.ShortSha(shaToMark)
menuItems := []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.NewTerm()),
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(shaToMark, info.NewTerm()); err != nil {
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
return self.c.Error(err)
}
@@ -87,10 +79,10 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Key: 'b',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.OldTerm()),
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(shaToMark, info.OldTerm()); err != nil {
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
return self.c.Error(err)
}
@@ -99,21 +91,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Key: 'g',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortShaToMark),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(shaToMark); err != nil {
return self.c.Error(err)
}
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 's',
},
}
if info.GetCurrentSha() != "" && info.GetCurrentSha() != commit.Sha {
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipSelected, commit.ShortSha()),
Label: fmt.Sprintf(self.c.Tr.Bisect.Skip, commit.ShortSha()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(commit.Sha); err != nil {
@@ -122,16 +100,16 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'S',
}))
}
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
Key: 's',
},
Key: 'r',
}))
{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
},
Key: 'r',
},
}
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Bisect.BisectMenuTitle,
@@ -175,28 +153,6 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
},
Key: 'g',
},
{
Label: self.c.Tr.Bisect.ChooseTerms,
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.Bisect.OldTermPrompt,
HandleConfirm: func(oldTerm string) error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.Bisect.NewTermPrompt,
HandleConfirm: func(newTerm string) error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.c.Git().Bisect.StartWithTerms(oldTerm, newTerm); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
})
},
})
},
Key: 't',
},
},
})
}

View File

@@ -114,7 +114,7 @@ func (self *CommitFilesController) GetOnRenderToMain() func() error {
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(
from, to, reverse, node.GetPath(), false, self.c.GetAppState().IgnoreWhitespaceInDiffView,
from, to, reverse, node.GetPath(), false, self.c.State().GetIgnoreWhitespaceInDiffView(),
)
task := types.NewRunPtyTask(cmdObj.GetCmd())

View File

@@ -3,6 +3,7 @@ package controllers
import (
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -40,7 +41,7 @@ func (self *CustomCommandAction) Call() error {
func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
// reversing so that we display the latest command first
history := lo.Reverse(self.c.GetAppState().CustomCommandsHistory)
history := slices.Reverse(self.c.GetAppState().CustomCommandsHistory)
return helpers.FuzzySearchFunc(history)
}

View File

@@ -201,7 +201,7 @@ func (self *FilesController) GetOnRenderToMain() func() error {
split := self.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
mainShowsStaged := !split && node.GetHasStagedChanges()
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, self.c.GetAppState().IgnoreWhitespaceInDiffView)
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, self.c.State().GetIgnoreWhitespaceInDiffView())
title := self.c.Tr.UnstagedChanges
if mainShowsStaged {
title = self.c.Tr.StagedChanges
@@ -216,7 +216,7 @@ func (self *FilesController) GetOnRenderToMain() func() error {
}
if split {
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, self.c.GetAppState().IgnoreWhitespaceInDiffView)
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, self.c.State().GetIgnoreWhitespaceInDiffView())
title := self.c.Tr.StagedChanges
if mainShowsStaged {

View File

@@ -61,45 +61,17 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
self.clearConfirmationViewKeyBindings()
}
// Temporary hack: we're just duplicating the logic in `gocui.lineWrap`
func getMessageHeight(wrap bool, message string, width int) int {
if !wrap {
return len(strings.Split(message, "\n"))
}
lineCount := 0
lines := strings.Split(message, "\n")
for _, line := range lines {
n := 0
lastWhitespaceIndex := -1
for i, currChr := range line {
rw := runewidth.RuneWidth(currChr)
n += rw
if n > width {
if currChr == ' ' {
n = 0
} else if currChr == '-' {
n = rw
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
if line[lastWhitespaceIndex] == '-' {
n = i - lastWhitespaceIndex
} else {
n = i - lastWhitespaceIndex + 1
}
} else {
n = rw
}
lineCount++
lastWhitespaceIndex = -1
} else if currChr == ' ' || currChr == '-' {
lastWhitespaceIndex = i
}
lineCount := 0
// if we need to wrap, calculate height to fit content within view's width
if wrap {
for _, line := range lines {
lineCount += runewidth.StringWidth(line)/width + 1
}
lineCount++
} else {
lineCount = len(lines)
}
return lineCount
}

View File

@@ -29,7 +29,7 @@ func (self *DiffHelper) DiffArgs() []string {
output = append(output, "-R")
}
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
if self.c.State().GetIgnoreWhitespaceInDiffView() {
output = append(output, "--ignore-all-space")
}
@@ -113,7 +113,7 @@ func (self *DiffHelper) WithDiffModeCheck(f func() error) error {
}
func (self *DiffHelper) IgnoringWhitespaceSubTitle() string {
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
if self.c.State().GetIgnoreWhitespaceInDiffView() {
return self.c.Tr.IgnoreWhitespaceDiffViewSubTitle
}

View File

@@ -4,12 +4,11 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type MergeAndRebaseHelper struct {
@@ -52,7 +51,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
})
}
menuItems := lo.Map(options, func(row optionAndKey, _ int) *types.MenuItem {
menuItems := slices.Map(options, func(row optionAndKey) *types.MenuItem {
return &types.MenuItem{
Label: row.option,
OnPress: func() error {
@@ -225,20 +224,8 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
Key: 's',
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
baseCommit := self.c.Modes().MarkedBaseCommit.GetSha()
var err error
if baseCommit != "" {
err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(ref, baseCommit)
} else {
err = self.c.Git().Rebase.RebaseBranch(ref)
}
err = self.CheckMergeOrRebase(err)
if err == nil {
self.c.Modes().MarkedBaseCommit.Reset()
}
return err
})
err := self.c.Git().Rebase.RebaseBranch(ref)
return self.CheckMergeOrRebase(err)
},
},
{
@@ -247,26 +234,17 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
Tooltip: self.c.Tr.InteractiveRebaseTooltip,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
baseCommit := self.c.Modes().MarkedBaseCommit.GetSha()
var err error
if baseCommit != "" {
err = self.c.Git().Rebase.EditRebaseFromBaseCommit(ref, baseCommit)
} else {
err = self.c.Git().Rebase.EditRebase(ref)
}
err := self.c.Git().Rebase.EditRebase(ref)
if err = self.CheckMergeOrRebase(err); err != nil {
return err
}
self.c.Modes().MarkedBaseCommit.Reset()
return self.c.PushContext(self.c.Contexts().LocalCommits)
},
},
}
title := utils.ResolvePlaceholderString(
lo.Ternary(self.c.Modes().MarkedBaseCommit.GetSha() != "",
self.c.Tr.RebasingFromBaseCommitTitle,
self.c.Tr.RebasingTitle),
self.c.Tr.RebasingTitle,
map[string]string{
"checkedOutBranch": checkedOutBranch,
"ref": ref,
@@ -305,8 +283,3 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
},
})
}
func (self *MergeAndRebaseHelper) ResetMarkedBaseCommit() error {
self.c.Modes().MarkedBaseCommit.Reset()
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}

View File

@@ -4,11 +4,11 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type ModeHelper struct {
@@ -82,16 +82,6 @@ func (self *ModeHelper) Statuses() []ModeStatus {
},
Reset: self.ExitFilterMode,
},
{
IsActive: self.c.Modes().MarkedBaseCommit.Active,
Description: func() string {
return self.withResetButton(
self.c.Tr.MarkedBaseCommitStatus,
style.FgCyan,
)
},
Reset: self.mergeAndRebaseHelper.ResetMarkedBaseCommit,
},
{
IsActive: self.c.Modes().CherryPicking.Active,
Description: func() string {
@@ -145,13 +135,13 @@ func (self *ModeHelper) withResetButton(content string, textStyle style.TextStyl
}
func (self *ModeHelper) GetActiveMode() (ModeStatus, bool) {
return lo.Find(self.Statuses(), func(mode ModeStatus) bool {
return slices.Find(self.Statuses(), func(mode ModeStatus) bool {
return mode.IsActive()
})
}
func (self *ModeHelper) IsAnyModeActive() bool {
return lo.SomeBy(self.Statuses(), func(mode ModeStatus) bool {
return slices.Some(self.Statuses(), func(mode ModeStatus) bool {
return mode.IsActive()
})
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -17,7 +18,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type RefreshHelper struct {
@@ -28,6 +28,7 @@ type RefreshHelper struct {
stagingHelper *StagingHelper
mergeConflictsHelper *MergeConflictsHelper
worktreeHelper *WorktreeHelper
fileWatcher types.IFileWatcher
}
func NewRefreshHelper(
@@ -38,6 +39,7 @@ func NewRefreshHelper(
stagingHelper *StagingHelper,
mergeConflictsHelper *MergeConflictsHelper,
worktreeHelper *WorktreeHelper,
fileWatcher types.IFileWatcher,
) *RefreshHelper {
return &RefreshHelper{
c: c,
@@ -47,6 +49,7 @@ func NewRefreshHelper(
stagingHelper: stagingHelper,
mergeConflictsHelper: mergeConflictsHelper,
worktreeHelper: worktreeHelper,
fileWatcher: fileWatcher,
}
}
@@ -93,10 +96,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
wg := sync.WaitGroup{}
refresh := func(name string, f func()) {
// if we're in a demo we don't want any async refreshes because
// everything happens fast and it's better to have everything update
// in the one frame
if !self.c.InDemo() && options.Mode == types.ASYNC {
if options.Mode == types.ASYNC {
self.c.OnWorker(func(t gocui.Task) {
f()
})
@@ -211,7 +211,7 @@ func getScopeNames(scopes []types.RefreshableView) []string {
types.MERGE_CONFLICTS: "mergeConflicts",
}
return lo.Map(scopes, func(scope types.RefreshableView, _ int) string {
return slices.Map(scopes, func(scope types.RefreshableView) string {
return scopeNameMap[scope]
})
}
@@ -272,32 +272,6 @@ func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
}
}
func (self *RefreshHelper) determineCheckedOutBranchName() string {
if rebasedBranch := self.c.Git().Status.BranchBeingRebased(); rebasedBranch != "" {
// During a rebase we're on a detached head, so cannot determine the
// branch name in the usual way. We need to read it from the
// ".git/rebase-merge/head-name" file instead.
return strings.TrimPrefix(rebasedBranch, "refs/heads/")
}
if bisectInfo := self.c.Git().Bisect.GetInfo(); bisectInfo.Bisecting() && bisectInfo.GetStartSha() != "" {
// Likewise, when we're bisecting we're on a detached head as well. In
// this case we read the branch name from the ".git/BISECT_START" file.
return bisectInfo.GetStartSha()
}
// In all other cases, get the branch name by asking git what branch is
// checked out. Note that if we're on a detached head (for reasons other
// than rebasing or bisecting, i.e. it was explicitly checked out), then
// this will return its sha.
if branchName, err := self.c.Git().Branch.CurrentBranchName(); err == nil {
return branchName
}
// Should never get here unless the working copy is corrupt
return ""
}
func (self *RefreshHelper) refreshCommitsWithLimit() error {
self.c.Mutexes().LocalCommitsMutex.Lock()
defer self.c.Mutexes().LocalCommitsMutex.Unlock()
@@ -317,7 +291,6 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
self.c.Model().Commits = commits
self.RefreshAuthors(commits)
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
self.c.Model().CheckedOutBranch = self.determineCheckedOutBranchName()
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
@@ -439,12 +412,6 @@ func (self *RefreshHelper) refreshBranches() {
self.c.Log.Error(err)
}
// Need to re-render the commits view because the visualization of local
// branch heads might have changed
if err := self.c.Contexts().LocalCommits.HandleRender(); err != nil {
self.c.Log.Error(err)
}
self.refreshStatus()
}
@@ -545,6 +512,10 @@ func (self *RefreshHelper) refreshStateFiles() error {
fileTreeViewModel.SetTree()
fileTreeViewModel.RWMutex.Unlock()
if err := self.fileWatcher.AddFilesToFileWatcher(files); err != nil {
return err
}
return nil
}

View File

@@ -4,13 +4,13 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type IRefsHelper interface {
@@ -132,7 +132,7 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error {
{strength: "hard", label: "Hard reset", key: 'h'},
}
menuItems := lo.Map(strengths, func(row strengthWithKey, _ int) *types.MenuItem {
menuItems := slices.Map(strengths, func(row strengthWithKey) *types.MenuItem {
return &types.MenuItem{
LabelColumns: []string{
row.label,

View File

@@ -7,6 +7,7 @@ import (
"strings"
"sync"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/commands"
@@ -17,7 +18,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type onNewRepoFn func(startArgs appTypes.StartArgs, contextKey types.ContextKey) error
@@ -115,7 +115,7 @@ func (self *ReposHelper) CreateRecentReposMenu() error {
wg.Wait()
menuItems := lo.Map(recentRepoPaths, func(path string, _ int) *types.MenuItem {
menuItems := slices.Map(recentRepoPaths, func(path string) *types.MenuItem {
branchName, _ := currentBranches.Load(path)
if icons.IsIconEnabled() {
branchName = icons.BRANCH_ICON + " " + fmt.Sprintf("%v", branchName)

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -11,7 +12,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/jesseduffield/minimal/gitignore"
"github.com/samber/lo"
"golang.org/x/exp/slices"
"gopkg.in/ozeidan/fuzzy-patricia.v3/patricia"
)
@@ -48,13 +48,13 @@ func NewSuggestionsHelper(
}
func (self *SuggestionsHelper) getRemoteNames() []string {
return lo.Map(self.c.Model().Remotes, func(remote *models.Remote, _ int) string {
return slices.Map(self.c.Model().Remotes, func(remote *models.Remote) string {
return remote.Name
})
}
func matchesToSuggestions(matches []string) []*types.Suggestion {
return lo.Map(matches, func(match string, _ int) *types.Suggestion {
return slices.Map(matches, func(match string) *types.Suggestion {
return &types.Suggestion{
Value: match,
Label: match,
@@ -69,7 +69,7 @@ func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.
}
func (self *SuggestionsHelper) getBranchNames() []string {
return lo.Map(self.c.Model().Branches, func(branch *models.Branch, _ int) string {
return slices.Map(self.c.Model().Branches, func(branch *models.Branch) string {
return branch.Name
})
}
@@ -85,7 +85,7 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
matchingBranchNames = utils.FuzzySearch(input, branchNames)
}
return lo.Map(matchingBranchNames, func(branchName string, _ int) *types.Suggestion {
return slices.Map(matchingBranchNames, func(branchName string) *types.Suggestion {
return &types.Suggestion{
Value: branchName,
Label: presentation.GetBranchTextStyle(branchName).Sprint(branchName),
@@ -141,8 +141,8 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
}
func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
return lo.FlatMap(self.c.Model().Remotes, func(remote *models.Remote, _ int) []string {
return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string {
return slices.FlatMap(self.c.Model().Remotes, func(remote *models.Remote) []string {
return slices.Map(remote.Branches, func(branch *models.RemoteBranch) string {
return fmt.Sprintf("%s%s%s", remote.Name, separator, branch.Name)
})
})
@@ -153,7 +153,7 @@ func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string
}
func (self *SuggestionsHelper) getTagNames() []string {
return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string {
return slices.Map(self.c.Model().Tags, func(tag *models.Tag) string {
return tag.Name
})
}

View File

@@ -4,7 +4,6 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type TagsHelper struct {
@@ -20,16 +19,16 @@ func NewTagsHelper(c *HelperCommon, commitsHelper *CommitsHelper) *TagsHelper {
}
func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
doCreateTag := func(tagName string, description string, force bool) error {
onConfirm := func(tagName string, description string) error {
return self.c.WithWaitingStatus(self.c.Tr.CreatingTag, func(gocui.Task) error {
if description != "" {
self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, description, force); err != nil {
if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, description); err != nil {
return self.c.Error(err)
}
} else {
self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
if err := self.c.Git().Tag.CreateLightweight(tagName, ref, force); err != nil {
if err := self.c.Git().Tag.CreateLightweight(tagName, ref); err != nil {
return self.c.Error(err)
}
}
@@ -42,28 +41,6 @@ func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
})
}
onConfirm := func(tagName string, description string) error {
if self.c.Git().Tag.HasTag(tagName) {
prompt := utils.ResolvePlaceholderString(
self.c.Tr.ForceTagPrompt,
map[string]string{
"tagName": tagName,
"cancelKey": self.c.UserConfig.Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig.Keybinding.Universal.Confirm,
},
)
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.ForceTag,
Prompt: prompt,
HandleConfirm: func() error {
return doCreateTag(tagName, description, true)
},
})
} else {
return doCreateTag(tagName, description, false)
}
}
return self.commitsHelper.OpenCommitMessagePanel(
&OpenCommitMessagePanelOpts{
CommitIndex: context.NoCommitIndex,

View File

@@ -3,8 +3,8 @@ package helpers
import (
"testing"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
@@ -25,7 +25,7 @@ func TestGetSuggestedRemote(t *testing.T) {
}
func mkRemoteList(names ...string) []*models.Remote {
return lo.Map(names, func(name string, _ int) *models.Remote {
return slices.Map(names, func(name string) *models.Remote {
return &models.Remote{Name: name}
})
}

View File

@@ -201,18 +201,12 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
appStatusBox.Weight = 1
} else {
optionsBox.Weight = 1
if self.c.InDemo() {
// app status appears very briefly in demos and dislodges the caption,
// so better not to show it at all
appStatusBox.Size = 0
} else {
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
}
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
}
result := []*boxlayout.Box{appStatusBox, optionsBox}
if (!self.c.InDemo() && self.c.UserConfig.Gui.ShowBottomLine) || self.modeHelper.IsAnyModeActive() {
if self.c.UserConfig.Gui.ShowBottomLine || self.modeHelper.IsAnyModeActive() {
result = append(result, &boxlayout.Box{
Window: "information",
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length

View File

@@ -100,15 +100,9 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
},
{
Key: opts.GetKey(opts.Config.Commits.PasteCommits),
Handler: self.paste,
Handler: opts.Guards.OutsideFilterMode(self.paste),
Description: self.c.Tr.PasteCommits,
},
{
Key: opts.GetKey(opts.Config.Commits.MarkCommitAsBaseForRebase),
Handler: self.checkSelected(self.markAsBaseCommit),
Description: self.c.Tr.MarkAsBaseCommit,
Tooltip: self.c.Tr.MarkAsBaseCommitTooltip,
},
// overriding these navigation keybindings because we might need to load
// more commits on demand
{
@@ -177,7 +171,7 @@ func (self *LocalCommitsController) GetOnRenderToMain() func() error {
"ref": commit.Name,
}))
} else {
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.GetAppState().IgnoreWhitespaceInDiffView)
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
@@ -421,15 +415,10 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoComma
}
self.c.LogAction("Update rebase TODO")
msg := utils.ResolvePlaceholderString(
self.c.Tr.Log.HandleMidRebaseCommand,
map[string]string{
"shortSha": commit.ShortSha(),
"action": action.String(),
},
self.c.LogCommand(
fmt.Sprintf("Updating rebase action of commit %s to '%s'", commit.ShortSha(), action.String()),
false,
)
self.c.LogCommand(msg, false)
if err := self.c.Git().Rebase.EditRebaseTodo(commit, action); err != nil {
return false, self.c.Error(err)
@@ -457,14 +446,7 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
// logging directly here because MoveTodoDown doesn't have enough information
// to provide a useful log
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
msg := utils.ResolvePlaceholderString(
self.c.Tr.Log.MovingCommitDown,
map[string]string{
"shortSha": commit.ShortSha(),
},
)
self.c.LogCommand(msg, false)
self.c.LogCommand(fmt.Sprintf("Moving commit %s down", commit.ShortSha()), false)
if err := self.c.Git().Rebase.MoveTodoDown(commit); err != nil {
return self.c.Error(err)
@@ -499,13 +481,10 @@ func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
// logging directly here because MoveTodoDown doesn't have enough information
// to provide a useful log
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
msg := utils.ResolvePlaceholderString(
self.c.Tr.Log.MovingCommitUp,
map[string]string{
"shortSha": commit.ShortSha(),
},
self.c.LogCommand(
fmt.Sprintf("Moving commit %s up", commit.ShortSha()),
false,
)
self.c.LogCommand(msg, false)
if err := self.c.Git().Rebase.MoveTodoUp(self.c.Model().Commits[index]); err != nil {
return self.c.Error(err)
@@ -861,16 +840,6 @@ func (self *LocalCommitsController) paste() error {
return self.c.Helpers().CherryPick.Paste()
}
func (self *LocalCommitsController) markAsBaseCommit(commit *models.Commit) error {
if commit.Sha == self.c.Modes().MarkedBaseCommit.GetSha() {
// Reset when invoking it again on the marked commit
self.c.Modes().MarkedBaseCommit.SetSha("")
} else {
self.c.Modes().MarkedBaseCommit.SetSha(commit.Sha)
}
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
func (self *LocalCommitsController) isHeadCommit() bool {
return models.IsHeadCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx())
}

View File

@@ -215,7 +215,7 @@ func (self *MergeConflictsController) HandleUndo() error {
}
self.c.LogAction("Restoring file to previous state")
self.c.LogCommand(self.c.Tr.Log.HandleUndo, false)
self.c.LogCommand("Undoing last conflict resolution", false)
if err := os.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
return err
}

View File

@@ -1,6 +1,7 @@
package controllers
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -20,7 +21,7 @@ func (self *OptionsMenuAction) Call() error {
bindings := self.getBindings(ctx)
menuItems := lo.Map(bindings, func(binding *types.Binding, _ int) *types.MenuItem {
menuItems := slices.Map(bindings, func(binding *types.Binding) *types.MenuItem {
return &types.MenuItem{
OpensMenu: binding.OpensMenu,
Label: binding.Description,

View File

@@ -37,7 +37,7 @@ func (self *ReflogCommitsController) GetOnRenderToMain() func() error {
if commit == nil {
task = types.NewRenderStringTask("No reflog history")
} else {
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.GetAppState().IgnoreWhitespaceInDiffView)
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}

View File

@@ -66,7 +66,7 @@ func (self *StashController) GetOnRenderToMain() func() error {
task = types.NewRunPtyTask(
self.c.Git().Stash.ShowStashEntryCmdObj(
stashEntry.Index,
self.c.GetAppState().IgnoreWhitespaceInDiffView,
self.c.State().GetIgnoreWhitespaceInDiffView(),
).GetCmd(),
)
}

View File

@@ -5,12 +5,12 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type StatusController struct {
@@ -155,7 +155,7 @@ func (self *StatusController) askForConfigFile(action func(file string) error) e
case 1:
return action(confPaths[0])
default:
menuItems := lo.Map(confPaths, func(path string, _ int) *types.MenuItem {
menuItems := slices.Map(confPaths, func(path string) *types.MenuItem {
return &types.MenuItem{
Label: path,
OnPress: func() error {

View File

@@ -38,7 +38,7 @@ func (self *SubCommitsController) GetOnRenderToMain() func() error {
if commit == nil {
task = types.NewRenderStringTask("No commits")
} else {
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.GetAppState().IgnoreWhitespaceInDiffView)
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}

View File

@@ -102,7 +102,7 @@ func (self *SubmodulesController) GetOnRenderToMain() func() error {
if file == nil {
task = types.NewRenderStringTask(prefix)
} else {
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, self.c.GetAppState().IgnoreWhitespaceInDiffView)
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, self.c.State().GetIgnoreWhitespaceInDiffView())
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
}
}

View File

@@ -11,7 +11,6 @@ var _ types.IController = &SwitchToSubCommitsController{}
type CanSwitchToSubCommits interface {
types.Context
GetSelectedRef() types.Ref
ShowBranchHeadsInSubCommits() bool
}
type SwitchToSubCommitsController struct {
@@ -80,7 +79,6 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
subCommitsContext.SetTitleRef(ref.Description())
subCommitsContext.SetRef(ref)
subCommitsContext.SetLimitCommits(true)
subCommitsContext.SetShowBranchHeads(self.context.ShowBranchHeadsInSubCommits())
subCommitsContext.ClearSearchString()
subCommitsContext.GetView().ClearSearch()

View File

@@ -8,7 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type SyncController struct {
@@ -187,7 +186,7 @@ func (self *SyncController) pushAux(opts pushOpts) error {
}
_ = self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.ForcePush,
Prompt: self.forcePushPrompt(),
Prompt: self.c.Tr.ForcePushPrompt,
HandleConfirm: func() error {
newOpts := opts
newOpts.force = true
@@ -211,20 +210,10 @@ func (self *SyncController) requestToForcePush(opts pushOpts) error {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.ForcePush,
Prompt: self.forcePushPrompt(),
Prompt: self.c.Tr.ForcePushPrompt,
HandleConfirm: func() error {
opts.force = true
return self.pushAux(opts)
},
})
}
func (self *SyncController) forcePushPrompt() string {
return utils.ResolvePlaceholderString(
self.c.Tr.ForcePushPrompt,
map[string]string{
"cancelKey": self.c.UserConfig.Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig.Keybinding.Universal.Confirm,
},
)
}

View File

@@ -23,10 +23,7 @@ func (self *ToggleWhitespaceAction) Call() error {
return self.c.ErrorMsg(self.c.Tr.IgnoreWhitespaceNotSupportedHere)
}
self.c.GetAppState().IgnoreWhitespaceInDiffView = !self.c.GetAppState().IgnoreWhitespaceInDiffView
if err := self.c.SaveAppState(); err != nil {
self.c.Log.Errorf("error when saving app state: %v", err)
}
self.c.State().SetIgnoreWhitespaceInDiffView(!self.c.State().GetIgnoreWhitespaceInDiffView())
return self.c.CurrentSideContext().HandleFocus(types.OnFocusOpts{})
}

View File

@@ -1,13 +1,8 @@
package controllers
import (
"bytes"
"fmt"
"math"
"math/rand"
"time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -34,10 +29,6 @@ func (self *FilesController) createResetMenu() error {
return self.c.Error(err)
}
if self.c.UserConfig.Gui.AnimateExplosion {
self.animateExplosion()
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
Key: 'x',
@@ -144,102 +135,3 @@ func (self *FilesController) createResetMenu() error {
return self.c.Menu(types.CreateMenuOptions{Title: "", Items: menuItems})
}
func (self *FilesController) animateExplosion() {
self.Explode(self.c.Views().Files, func() {
err := self.c.PostRefreshUpdate(self.c.Contexts().Files)
if err != nil {
self.c.Log.Error(err)
}
})
}
// Animates an explosion within the view by drawing a bunch of flamey characters
func (self *FilesController) Explode(v *gocui.View, onDone func()) {
width := v.InnerWidth()
height := v.InnerHeight() + 1
styles := []style.TextStyle{
style.FgLightWhite.SetBold(),
style.FgYellow.SetBold(),
style.FgRed.SetBold(),
style.FgBlue.SetBold(),
style.FgBlack.SetBold(),
}
self.c.OnWorker(func(_ gocui.Task) {
max := 25
for i := 0; i < max; i++ {
image := getExplodeImage(width, height, i, max)
style := styles[(i*len(styles)/max)%len(styles)]
coloredImage := style.Sprint(image)
self.c.OnUIThread(func() error {
v.SetContent(coloredImage)
return nil
})
time.Sleep(time.Millisecond * 20)
}
self.c.OnUIThread(func() error {
v.Clear()
onDone()
return nil
})
})
}
// Render an explosion in the given bounds.
func getExplodeImage(width int, height int, frame int, max int) string {
// Predefine the explosion symbols
explosionChars := []rune{'*', '.', '@', '#', '&', '+', '%'}
// Initialize a buffer to build our string
var buf bytes.Buffer
// Initialize RNG seed
random := rand.New(rand.NewSource(time.Now().UnixNano()))
// calculate the center of explosion
centerX, centerY := width/2, height/2
// calculate the max radius (hypotenuse of the view)
maxRadius := math.Hypot(float64(centerX), float64(centerY))
// calculate frame as a proportion of max, apply square root to create the non-linear effect
progress := math.Sqrt(float64(frame) / float64(max))
// calculate radius of explosion according to frame and max
radius := progress * maxRadius * 2
// introduce a new radius for the inner boundary of the explosion (the shockwave effect)
var innerRadius float64
if progress > 0.5 {
innerRadius = (progress - 0.5) * 2 * maxRadius
}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// calculate distance from center, scale x by 2 to compensate for character aspect ratio
distance := math.Hypot(float64(x-centerX), float64(y-centerY)*2)
// if distance is less than radius and greater than innerRadius, draw explosion char
if distance <= radius && distance >= innerRadius {
// Make placement random and less likely as explosion progresses
if random.Float64() > progress {
// Pick a random explosion char
char := explosionChars[random.Intn(len(explosionChars))]
buf.WriteRune(char)
} else {
buf.WriteRune(' ')
}
} else {
// If not explosion, then it's empty space
buf.WriteRune(' ')
}
}
// End of line
if y < height-1 {
buf.WriteRune('\n')
}
}
return buf.String()
}

View File

@@ -24,9 +24,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
show := !gui.c.State().GetShowExtrasWindow()
gui.c.State().SetShowExtrasWindow(show)
gui.c.GetAppState().HideCommandLog = !show
if err := gui.c.SaveAppState(); err != nil {
gui.c.Log.Errorf("error when saving app state: %v", err)
}
_ = gui.c.SaveAppState()
return nil
},
},

137
pkg/gui/file_watching.go Normal file
View File

@@ -0,0 +1,137 @@
package gui
import (
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
// macs for some bizarre reason cap the number of watchable files to 256.
// there's no obvious platform agnostic way to check the situation of the user's
// computer so we're just arbitrarily capping at 200. This isn't so bad because
// file watching is only really an added bonus for faster refreshing.
const MAX_WATCHED_FILES = 50
var _ types.IFileWatcher = new(fileWatcher)
type fileWatcher struct {
Watcher *fsnotify.Watcher
WatchedFilenames []string
Log *logrus.Entry
Disabled bool
}
func NewFileWatcher(log *logrus.Entry) *fileWatcher {
// TODO: get this going again, and ensure we don't see any crashes from it
return &fileWatcher{
Disabled: true,
}
}
func (w *fileWatcher) watchingFilename(filename string) bool {
for _, watchedFilename := range w.WatchedFilenames {
if watchedFilename == filename {
return true
}
}
return false
}
func (w *fileWatcher) popOldestFilename() {
// shift the last off the array to make way for this one
oldestFilename := w.WatchedFilenames[0]
w.WatchedFilenames = w.WatchedFilenames[1:]
if err := w.Watcher.Remove(oldestFilename); err != nil {
// swallowing errors here because it doesn't really matter if we can't unwatch a file
w.Log.Error(err)
}
}
func (w *fileWatcher) watchFilename(filename string) {
if err := w.Watcher.Add(filename); err != nil {
// swallowing errors here because it doesn't really matter if we can't watch a file
w.Log.Error(err)
}
// assume we're watching it now to be safe
w.WatchedFilenames = append(w.WatchedFilenames, filename)
}
func (w *fileWatcher) AddFilesToFileWatcher(files []*models.File) error {
if w.Disabled {
return nil
}
if len(files) == 0 {
return nil
}
// watch the files for changes
dirName, err := os.Getwd()
if err != nil {
return err
}
for _, file := range files[0:min(MAX_WATCHED_FILES, len(files))] {
if file.Deleted {
continue
}
filename := filepath.Join(dirName, file.Name)
if w.watchingFilename(filename) {
continue
}
if len(w.WatchedFilenames) > MAX_WATCHED_FILES {
w.popOldestFilename()
}
w.watchFilename(filename)
}
return nil
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
// NOTE: given that we often edit files ourselves, this may make us end up refreshing files too often
// TODO: consider watching the whole directory recursively (could be more expensive)
func (gui *Gui) WatchFilesForChanges() {
gui.fileWatcher = NewFileWatcher(gui.Log)
if gui.fileWatcher.Disabled {
return
}
go utils.Safe(func() {
for {
select {
// watch for events
case event := <-gui.fileWatcher.Watcher.Events:
if event.Op == fsnotify.Chmod {
// for some reason we pick up chmod events when they don't actually happen
continue
}
// only refresh if we're not already
if !gui.IsRefreshingFiles {
gui.c.OnUIThread(func() error {
// TODO: find out if refresh needs to be run on the UI thread
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
})
}
// watch for errors
case err := <-gui.fileWatcher.Watcher.Errors:
if err != nil {
gui.c.Log.Error(err)
}
}
}
})
}

View File

@@ -1,8 +1,8 @@
package filetree
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
)
@@ -60,7 +60,7 @@ func (self *CommitFileTree) GetAllItems() []*CommitFileNode {
}
// ignoring root
return lo.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.CommitFile], _ int) *CommitFileNode {
return slices.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.CommitFile]) *CommitFileNode {
return NewCommitFileNode(node)
})
}

View File

@@ -3,8 +3,8 @@ package filetree
import (
"fmt"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
)
@@ -88,7 +88,7 @@ func (self *FileTree) getFilesForDisplay() []*models.File {
}
func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File {
return lo.Filter(self.getFiles(), func(file *models.File, _ int) bool { return test(file) })
return slices.Filter(self.getFiles(), test)
}
func (self *FileTree) SetStatusFilter(filter FileTreeDisplayFilter) {
@@ -130,7 +130,7 @@ func (self *FileTree) GetAllItems() []*FileNode {
}
// ignoring root
return lo.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.File], _ int) *FileNode {
return slices.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.File]) *FileNode {
return NewFileNode(node)
})
}

View File

@@ -1,10 +1,9 @@
package filetree
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
// Represents a file or directory in a file tree.
@@ -153,7 +152,7 @@ func (self *Node[T]) Flatten(collapsedPaths *CollapsedPaths) []*Node[T] {
result := []*Node[T]{self}
if len(self.Children) > 0 && !collapsedPaths.IsCollapsed(self.GetPath()) {
result = append(result, lo.FlatMap(self.Children, func(child *Node[T], _ int) []*Node[T] {
result = append(result, slices.FlatMap(self.Children, func(child *Node[T]) []*Node[T] {
return child.Flatten(collapsedPaths)
})...)
}
@@ -274,11 +273,11 @@ func (self *Node[T]) GetPathsMatching(test func(*Node[T]) bool) []string {
}
func (self *Node[T]) GetFilePathsMatching(test func(*T) bool) []string {
matchingFileNodes := lo.Filter(self.GetLeaves(), func(node *Node[T], _ int) bool {
matchingFileNodes := slices.Filter(self.GetLeaves(), func(node *Node[T]) bool {
return test(node.File)
})
return lo.Map(matchingFileNodes, func(node *Node[T], _ int) string {
return slices.Map(matchingFileNodes, func(node *Node[T]) string {
return node.GetPath()
})
}
@@ -288,7 +287,7 @@ func (self *Node[T]) GetLeaves() []*Node[T] {
return []*Node[T]{self}
}
return lo.FlatMap(self.Children, func(child *Node[T], _ int) []*Node[T] {
return slices.FlatMap(self.Children, func(child *Node[T]) []*Node[T] {
return child.GetLeaves()
})
}

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