Compare commits

...

33 Commits

Author SHA1 Message Date
Jesse Duffield
058bcddc53 fix renamed files looking wrong 2021-03-14 13:24:51 +11:00
Jesse Duffield
8288de0c84 update release notes 2021-03-13 11:46:48 +11:00
Ryooooooga
1da2afd450 Fix edit remote name message 2021-03-13 11:04:38 +11:00
Jesse Duffield
03de51747e remove redundant addition 2021-03-13 11:03:34 +11:00
Ryooooooga
3d698cd7c1 Fix tests 2021-03-13 11:02:31 +11:00
Ryooooooga
a48cc245e7 Support multibyte characters in pane 2021-03-13 11:02:31 +11:00
Ryooooooga
9ed3a8ee05 Fix staging/unstaging filenames that starts with - or -- 2021-03-13 11:02:31 +11:00
Ryooooooga
64daf1310d Fix staging/unstaging files containing " in paths 2021-03-13 11:02:31 +11:00
Ryooooooga
e5ba0d9d9c Support multibyte characters in Files pane 2021-03-13 11:02:31 +11:00
Ryooooooga
50e4e9d58d fix command escaping 2021-03-13 10:49:40 +11:00
István Donkó
03b9db5e0a Fix the linux config path (related: #913, #1059) 2021-03-12 12:45:48 +11:00
Jesse Duffield
043cb2ea44 reload config whenever returning to gui 2021-02-24 02:45:05 -08:00
Dawid Dziurla
a62d70fbd5 Merge pull request #1172 from jesseduffield/dawidd6-patch-1
gui: ReplaceAll -> Replace
2021-02-24 00:14:24 +01:00
Dawid Dziurla
053e80a08e gui: ReplaceAll -> Replace 2021-02-24 00:09:05 +01:00
Daniel Bast
b726dcc770 Switch to Go 1.16 to support macOS arm64 2021-02-23 03:04:49 -08:00
Matthias Küch
9df133ed8c Fix pattern in commitPrefix example 2021-02-16 13:57:28 -08:00
1jz
50dd7b00c3 add colors to differentiate action and menu commands 2021-02-16 13:52:04 -08:00
Rui Pires
ccbd2c924b Fixed whitespace format issue 2021-02-09 14:45:33 -08:00
Rui Pires
52d5c3beeb Added initialContent on branch rename 2021-02-09 14:45:33 -08:00
Jesse Duffield
c43416891e update cheatsheet 2021-02-09 20:23:20 +11:00
Jesse Duffield
56a573de86 support wide characters in the editor 2021-02-08 22:55:11 +00:00
Jesse Duffield
e7fff2529c fix lint error 2021-02-08 14:40:30 -08:00
Jesse Duffield
78867647d1 remove go-gitconfig package 2021-02-08 14:40:30 -08:00
Jesse Duffield
09f32d4f84 add secureexec file for getting around windows checking for a binary first in the current dir 2021-02-08 14:40:30 -08:00
Nick Flueckiger
6f0f70bd92 Adding setup and config 2021-02-08 14:25:24 -08:00
caquillo07
6df15ddf6e added support for using spaces on branch names when creating new ones. 2021-02-08 14:23:54 -08:00
unknown
922c0887f1 fix type: executable not found error when there is a merge conflict on windows 2021-01-01 13:17:29 -08:00
Dawid Dziurla
d7c9243880 workflows: setup git before PPA repo updating 2020-12-24 10:32:50 +01:00
Dawid Dziurla
f42a595aba Merge pull request #1130 from jesseduffield/dawidd6-patch-2
gui: ReplaceAll -> Replace
2020-12-24 10:24:49 +01:00
Dawid Dziurla
797722ec12 Merge pull request #1129 from jesseduffield/dawidd6-patch-1
workflows: split CD into separate jobs
2020-12-24 10:24:30 +01:00
Dawid Dziurla
bb4bf23c5c gui: ReplaceAll -> Replace 2020-12-24 10:21:54 +01:00
Dawid Dziurla
f3aacbd253 workflows: split CD into separate jobs 2020-12-24 09:51:50 +01:00
Jeff Hertzler
106fce26b5 Update Custom_Command_Keybindings.md 2020-12-23 18:54:26 -08:00
63 changed files with 2769 additions and 688 deletions

View File

@@ -6,8 +6,8 @@ on:
- 'v*'
jobs:
cd:
runs-on: ubuntu-20.04
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
@@ -16,22 +16,30 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.14.x
go-version: 1.16.x
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v1
env:
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
- name: Bump Homebrew
homebrew:
runs-on: macos-latest
steps:
- name: Bump Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@v3
with:
token: ${{secrets.GITHUB_API_TOKEN}}
formula: lazygit
ppa:
runs-on: ubuntu-20.04
steps:
- name: Checkout PPA repo
uses: actions/checkout@v2
with:
repository: dawidd6/lazygit-debian
token: ${{secrets.GITHUB_API_TOKEN}}
fetch-depth: 0
- name: Setup git
uses: dawidd6/action-git-user-config@v1
- name: Update PPA repo
run: |
version="$(echo "$GITHUB_REF" | sed 's@refs/tags/v@@')"

View File

@@ -17,7 +17,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.14.x
go-version: 1.16.x
- name: Cache build
uses: actions/cache@v1
with:

View File

@@ -2,7 +2,7 @@
Default path for the config file:
* Linux: `~/.config/jesseduffield/lazygit/config.yml`
* Linux: `~/.config/lazygit/config.yml`
* MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
* Windows: `%APPDATA%\jesseduffield\lazygit\config.yml`
@@ -51,6 +51,9 @@ Default path for the config file:
allBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium"
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
disableForcePushing: false
refresher:
refreshInterval: 10 # file/submodule refresh interval in seconds
fetchInterval: 60 # re-fetch interval in seconds
update:
method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for
@@ -325,7 +328,7 @@ Example:
git:
commitPrefixes:
my_project: # This is repository folder name
pattern: "^\\w+\\/(\\w+-\\w+)"
pattern: "^\\w+\\/(\\w+-\\w+).*"
replace: "[$1] "
```

View File

@@ -123,7 +123,7 @@ SelectedCommitFile
CheckedOutBranch
```
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Sha}}` and `{{.SelectedBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model.
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Sha}}` and `{{.SelectedBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model.
### Keybinding collisions

View File

@@ -24,13 +24,13 @@
## List Panel Navigation
<pre>
<kbd>.</kbd>: next page
<kbd>,</kbd>: previous page
<kbd><</kbd>: scroll to top
<kbd>></kbd>: scroll to bottom
<kbd>/</kbd>: start search
<kbd>]</kbd>: next tab
<kbd>[</kbd>: previous tab
<kbd>,</kbd>: previous page
<kbd>.</kbd>: next page
<kbd><</kbd>: scroll to top
<kbd>/</kbd>: start search
<kbd>></kbd>: scroll to bottom
</pre>
## Branches Panel (Branches Tab)
@@ -38,6 +38,7 @@
<pre>
<kbd>space</kbd>: checkout
<kbd>o</kbd>: create pull request
<kbd>ctrl+y</kbd>: copy pull request URL to clipboard
<kbd>c</kbd>: checkout by name
<kbd>F</kbd>: force checkout
<kbd>n</kbd>: new branch
@@ -137,6 +138,7 @@
<kbd>n</kbd>: create new branch off of commit
<kbd>T</kbd>: tag commit
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>ctrl+y</kbd>: copy commit message to clipboard
</pre>
## Commits Panel (Reflog Tab)
@@ -267,4 +269,5 @@
<kbd>o</kbd>: open config file
<kbd>u</kbd>: check for update
<kbd>enter</kbd>: switch to a recent repo
<kbd>a</kbd>: show all branch logs
</pre>

View File

@@ -24,13 +24,13 @@
## List Panel Navigation
<pre>
<kbd>.</kbd>: volgende pagina
<kbd>,</kbd>: vorige pagina
<kbd><</kbd>: scroll naar boven
<kbd>></kbd>: scroll naar beneden
<kbd>/</kbd>: start met zoekken
<kbd>]</kbd>: volgende tab
<kbd>[</kbd>: vorige tab
<kbd>,</kbd>: vorige pagina
<kbd>.</kbd>: volgende pagina
<kbd><</kbd>: scroll naar boven
<kbd>/</kbd>: start met zoekken
<kbd>></kbd>: scroll naar beneden
</pre>
## Branches Paneel (Branches Tab)
@@ -38,6 +38,7 @@
<pre>
<kbd>space</kbd>: uitchecken
<kbd>o</kbd>: maak een pull-aanvraag
<kbd>ctrl+y</kbd>: kopieer de URL van het pull-verzoek naar het klembord
<kbd>c</kbd>: uitchecken bij naam
<kbd>F</kbd>: forceer checkout
<kbd>n</kbd>: nieuwe branch
@@ -137,6 +138,7 @@
<kbd>n</kbd>: create new branch off of commit
<kbd>T</kbd>: tag commit
<kbd>ctrl+r</kbd>: reset cherry-picked (gecopieerde) commits selectie
<kbd>ctrl+y</kbd>: copieer commit bericht naar clipboard
</pre>
## Commits Paneel (Reflog Tab)
@@ -267,4 +269,5 @@
<kbd>o</kbd>: open config bestand
<kbd>u</kbd>: check voor updates
<kbd>enter</kbd>: wissel naar een recente repo
<kbd>a</kbd>: alle takken van het houtblok laten zien
</pre>

View File

@@ -24,13 +24,13 @@
## List Panel Navigation
<pre>
<kbd>.</kbd>: next page
<kbd>,</kbd>: previous page
<kbd><</kbd>: scroll to top
<kbd>></kbd>: scroll to bottom
<kbd>/</kbd>: start search
<kbd>]</kbd>: next tab
<kbd>[</kbd>: previous tab
<kbd>,</kbd>: previous page
<kbd>.</kbd>: next page
<kbd><</kbd>: scroll to top
<kbd>/</kbd>: start search
<kbd>></kbd>: scroll to bottom
</pre>
## Gałęzie Panel (Branches Tab)
@@ -38,6 +38,7 @@
<pre>
<kbd>space</kbd>: przełącz
<kbd>o</kbd>: utwórz żądanie wyciągnięcia
<kbd>ctrl+y</kbd>: skopiuj adres URL żądania ściągnięcia do schowka
<kbd>c</kbd>: przełącz używając nazwy
<kbd>F</kbd>: wymuś przełączenie
<kbd>n</kbd>: nowa gałąź
@@ -137,6 +138,7 @@
<kbd>n</kbd>: create new branch off of commit
<kbd>T</kbd>: tag commit
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>ctrl+y</kbd>: copy commit message to clipboard
</pre>
## Commity Panel (Reflog Tab)
@@ -267,4 +269,5 @@
<kbd>o</kbd>: otwórz plik konfiguracyjny
<kbd>u</kbd>: sprawdź aktualizacje
<kbd>enter</kbd>: switch to a recent repo
<kbd>a</kbd>: pokazywać wszystkie logi branżowe
</pre>

8
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/OpenPeeDeeP/xdg v1.0.0
github.com/atotto/clipboard v0.1.2
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/fatih/color v1.9.0
@@ -18,22 +19,23 @@ require (
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
github.com/jesseduffield/gocui v0.3.1-0.20201010224802-8a6768078fd7
github.com/jesseduffield/gocui v0.3.1-0.20210208224444-2eecee85583d
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe
github.com/jesseduffield/yaml v2.1.0+incompatible
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-runewidth v0.0.9
github.com/mattn/go-runewidth v0.0.10
github.com/mgutz/str v1.2.0
github.com/nsf/termbox-go v0.0.0-20210114135735-d04385b850e8 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sahilm/fuzzy v0.1.0
github.com/sirupsen/logrus v1.4.2
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stretchr/testify v1.4.0
github.com/tcnksm/go-gitconfig v0.1.2
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20201005172224-997123666555 // indirect

16
go.sum
View File

@@ -11,6 +11,8 @@ github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn
github.com/aybabtme/humanlog v0.4.1 h1:D8d9um55rrthJsP8IGSHBcti9lTb/XknmDAX6Zy8tek=
github.com/aybabtme/humanlog v0.4.1/go.mod h1:B0bnQX4FTSU3oftPMTTPvENCy8LqixLDvYJA9TUCAGo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
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/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=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -63,8 +65,12 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20161105104656-d666c9f652af h1:9ZI/QyVOerYYeqMt4svycU2Lz0WvxNHCpHHbsFsi/oA=
github.com/jesseduffield/gocui v0.3.1-0.20161105104656-d666c9f652af/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/gocui v0.3.1-0.20201010224802-8a6768078fd7 h1:K3MGrjmpPtIhfXmKh/zsIF0CdmNKOkjpIwcUfAa/J2A=
github.com/jesseduffield/gocui v0.3.1-0.20201010224802-8a6768078fd7/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/gocui v0.3.1-0.20210208224444-2eecee85583d h1:Jto9W9w8CFwZiAYXa7LsHDEOb5cKCA1f5LOL1A3jva4=
github.com/jesseduffield/gocui v0.3.1-0.20210208224444-2eecee85583d/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe h1:qsVhCf2RFyyKIUe/+gJblbCpXMUki9rZrHuEctg6M/E=
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
@@ -101,12 +107,16 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
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=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsf/termbox-go v0.0.0-20210114135735-d04385b850e8 h1:3vzIuru1svOK2sXlg4XcrO3KkGRneIejmfQfR+ptSW8=
github.com/nsf/termbox-go v0.0.0-20210114135735-d04385b850e8/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -116,6 +126,10 @@ 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@@ -130,8 +144,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=

View File

@@ -8,7 +8,6 @@ import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
@@ -21,6 +20,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/updates"
"github.com/sirupsen/logrus"
)
@@ -324,7 +324,7 @@ func TailLogs() {
log.Fatal(err)
}
cmd := exec.Command("tail", "-f", logFilePath)
cmd := secureexec.Command("tail", "-f", logFilePath)
stdout, _ := cmd.StdoutPipe()
if err := cmd.Start(); err != nil {

View File

@@ -3,7 +3,6 @@ package commands
import (
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -25,7 +24,7 @@ func (c *GitCommand) Commit(message string, flags string) (*exec.Cmd, error) {
splitMessage := strings.Split(message, "\n")
lineArgs := ""
for _, line := range splitMessage {
lineArgs += fmt.Sprintf(" -m %s", strconv.Quote(line))
lineArgs += fmt.Sprintf(" -m %s", c.OSCommand.Quote(line))
}
command := fmt.Sprintf("git commit %s%s", flags, lineArgs)

View File

@@ -43,13 +43,6 @@ func (c *GitCommand) colorArg() string {
}
func (c *GitCommand) GetConfigValue(key string) string {
value, _ := c.getLocalGitConfig(key)
// we get an error if the key doesn't exist which we don't care about
if value != "" {
return value
}
value, _ = c.getGlobalGitConfig(key)
return value
output, _ := c.getGitConfigValue(key)
return output
}

View File

@@ -15,12 +15,11 @@ func NewDummyGitCommand() *GitCommand {
// NewDummyGitCommandWithOSCommand creates a new dummy GitCommand for testing
func NewDummyGitCommandWithOSCommand(osCommand *oscommands.OSCommand) *GitCommand {
return &GitCommand{
Log: utils.NewDummyLog(),
OSCommand: osCommand,
Tr: i18n.NewTranslationSet(utils.NewDummyLog()),
Config: config.NewDummyAppConfig(),
getGlobalGitConfig: func(string) (string, error) { return "", nil },
getLocalGitConfig: func(string) (string, error) { return "", nil },
removeFile: func(string) error { return nil },
Log: utils.NewDummyLog(),
OSCommand: osCommand,
Tr: i18n.NewTranslationSet(utils.NewDummyLog()),
Config: config.NewDummyAppConfig(),
getGitConfigValue: func(string) (string, error) { return "", nil },
removeFile: func(string) error { return nil },
}
}

View File

@@ -21,8 +21,8 @@ func (c *GitCommand) CatFile(fileName string) (string, error) {
// StageFile stages a file
func (c *GitCommand) StageFile(fileName string) error {
// renamed files look like "file1 -> file2"
fileNames := strings.Split(fileName, " -> ")
return c.OSCommand.RunCommand("git add %s", c.OSCommand.Quote(fileNames[len(fileNames)-1]))
fileNames := strings.Split(fileName, models.RENAME_SEPARATOR)
return c.OSCommand.RunCommand("git add -- %s", c.OSCommand.Quote(fileNames[len(fileNames)-1]))
}
// StageAll stages all files
@@ -37,13 +37,13 @@ func (c *GitCommand) UnstageAll() error {
// UnStageFile unstages a file
func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
command := "git rm --cached --force %s"
command := "git rm --cached --force -- %s"
if tracked {
command = "git reset HEAD %s"
command = "git reset HEAD -- %s"
}
// renamed files look like "file1 -> file2"
fileNames := strings.Split(fileName, " -> ")
fileNames := strings.Split(fileName, models.RENAME_SEPARATOR)
for _, name := range fileNames {
if err := c.OSCommand.RunCommand(command, c.OSCommand.Quote(name)); err != nil {
return err
@@ -63,7 +63,7 @@ func (c *GitCommand) BeforeAndAfterFileForRename(file *models.File) (*models.Fil
// all files, passing the --no-renames flag and then recursively call the function
// again for the before file and after file. At some point we should fix the abstraction itself
split := strings.Split(file.Name, " -> ")
split := strings.Split(file.Name, models.RENAME_SEPARATOR)
filesWithoutRenames := c.GetStatusFiles(GetStatusFileOptions{NoRenames: true})
var beforeFile *models.File
var afterFile *models.File
@@ -143,13 +143,13 @@ func (c *GitCommand) WorktreeFileDiffCmdStr(file *models.File, plain bool, cache
cachedArg := ""
trackedArg := "--"
colorArg := c.colorArg()
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
split := strings.Split(file.Name, models.RENAME_SEPARATOR) // in case of a renamed file we get the new filename
fileName := c.OSCommand.Quote(split[len(split)-1])
if cached {
cachedArg = "--cached"
}
if !file.Tracked && !file.HasStagedChanges && !cached {
trackedArg = "--no-index /dev/null"
trackedArg = "--no-index -- /dev/null"
}
if plain {
colorArg = "never"
@@ -268,7 +268,7 @@ func (c *GitCommand) ResetAndClean() error {
// EditFile opens a file in a subprocess using whatever editor is available,
// falling back to core.editor, VISUAL, EDITOR, then vi
func (c *GitCommand) EditFile(filename string) (*exec.Cmd, error) {
editor, _ := c.getGlobalGitConfig("core.editor")
editor := c.GetConfigValue("core.editor")
if editor == "" {
editor = c.OSCommand.Getenv("VISUAL")

View File

@@ -16,7 +16,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
gitconfig "github.com/tcnksm/go-gitconfig"
)
// this takes something like:
@@ -32,8 +31,7 @@ type GitCommand struct {
Repo *gogit.Repository
Tr *i18n.TranslationSet
Config config.AppConfigurer
getGlobalGitConfig func(string) (string, error)
getLocalGitConfig func(string) (string, error)
getGitConfigValue func(string) (string, error)
removeFile func(string) error
DotGitDir string
onSuccessfulContinue func() error
@@ -74,16 +72,15 @@ func NewGitCommand(log *logrus.Entry, osCommand *oscommands.OSCommand, tr *i18n.
}
gitCommand := &GitCommand{
Log: log,
OSCommand: osCommand,
Tr: tr,
Repo: repo,
Config: config,
getGlobalGitConfig: gitconfig.Global,
getLocalGitConfig: gitconfig.Local,
removeFile: os.RemoveAll,
DotGitDir: dotGitDir,
PushToCurrent: pushToCurrent,
Log: log,
OSCommand: osCommand,
Tr: tr,
Repo: repo,
Config: config,
getGitConfigValue: getGitConfigValue,
removeFile: os.RemoveAll,
DotGitDir: dotGitDir,
PushToCurrent: pushToCurrent,
}
gitCommand.PatchManager = patch.NewPatchManager(log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff)

View File

@@ -16,6 +16,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/test"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
@@ -269,7 +270,7 @@ func TestGitCommandGetStashEntries(t *testing.T) {
{
"No stash entries found",
func(string, ...string) *exec.Cmd {
return exec.Command("echo")
return secureexec.Command("echo")
},
func(entries []*models.StashEntry) {
assert.Len(t, entries, 0)
@@ -278,7 +279,7 @@ func TestGitCommandGetStashEntries(t *testing.T) {
{
"Several stash entries found",
func(string, ...string) *exec.Cmd {
return exec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template")
return secureexec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template")
},
func(entries []*models.StashEntry) {
expected := []*models.StashEntry{
@@ -320,7 +321,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
{
"No files found",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo")
return secureexec.Command("echo")
},
func(files []*models.File) {
assert.Len(t, files, 0)
@@ -329,7 +330,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
{
"Several files found",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command(
return secureexec.Command(
"echo",
"MM file1.txt\nA file3.txt\nAM file2.txt\n?? file4.txt\nUU file5.txt",
)
@@ -422,7 +423,7 @@ func TestGitCommandStashDo(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"stash", "drop", "stash@{1}"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.StashDo(1, "drop"))
@@ -435,7 +436,7 @@ func TestGitCommandStashSave(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"stash", "save", "A stash message"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.StashSave("A stash message"))
@@ -448,7 +449,7 @@ func TestGitCommandCommitAmend(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--amend", "--allow-empty"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
_, err := gitCmd.PrepareCommitAmendSubProcess().CombinedOutput()
@@ -548,7 +549,7 @@ func TestGitCommandGetCommitDifferences(t *testing.T) {
{
"Can't retrieve pushable count",
func(string, ...string) *exec.Cmd {
return exec.Command("test")
return secureexec.Command("test")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "?", pushableCount)
@@ -559,10 +560,10 @@ func TestGitCommandGetCommitDifferences(t *testing.T) {
"Can't retrieve pullable count",
func(cmd string, args ...string) *exec.Cmd {
if args[1] == "HEAD..@{u}" {
return exec.Command("test")
return secureexec.Command("test")
}
return exec.Command("echo")
return secureexec.Command("echo")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "?", pushableCount)
@@ -573,10 +574,10 @@ func TestGitCommandGetCommitDifferences(t *testing.T) {
"Retrieve pullable and pushable count",
func(cmd string, args ...string) *exec.Cmd {
if args[1] == "HEAD..@{u}" {
return exec.Command("echo", "10")
return secureexec.Command("echo", "10")
}
return exec.Command("echo", "11")
return secureexec.Command("echo", "11")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "11", pushableCount)
@@ -601,7 +602,7 @@ func TestGitCommandRenameCommit(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--allow-empty", "--amend", "-m", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.RenameCommit("test"))
@@ -614,7 +615,7 @@ func TestGitCommandResetToCommit(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"reset", "--hard", "78976bc"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", oscommands.RunCommandOptions{}))
@@ -627,7 +628,7 @@ func TestGitCommandNewBranch(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"checkout", "-b", "test", "master"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.NewBranch("test", "master"))
@@ -652,7 +653,7 @@ func TestGitCommandDeleteBranch(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"branch", "-d", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -666,7 +667,7 @@ func TestGitCommandDeleteBranch(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"branch", "-D", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -690,7 +691,7 @@ func TestGitCommandMerge(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"merge", "--no-edit", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.Merge("test", MergeOpts{}))
@@ -699,45 +700,24 @@ func TestGitCommandMerge(t *testing.T) {
// TestGitCommandUsingGpg is a function.
func TestGitCommandUsingGpg(t *testing.T) {
type scenario struct {
testName string
getLocalGitConfig func(string) (string, error)
getGlobalGitConfig func(string) (string, error)
test func(bool)
testName string
getGitConfigValue func(string) (string, error)
test func(bool)
}
scenarios := []scenario{
{
"Option global and local config commit.gpgsign is not set",
func(string) (string, error) {
return "", nil
},
func(string) (string, error) {
return "", nil
},
func(string) (string, error) { return "", nil },
func(gpgEnabled bool) {
assert.False(t, gpgEnabled)
},
},
{
"Option global config commit.gpgsign is not set, fallback on local config",
func(string) (string, error) {
return "", nil
},
func(string) (string, error) {
return "true", nil
},
func(gpgEnabled bool) {
assert.True(t, gpgEnabled)
},
},
{
"Option commit.gpgsign is true",
func(string) (string, error) {
return "True", nil
},
func(string) (string, error) {
return "", nil
},
func(gpgEnabled bool) {
assert.True(t, gpgEnabled)
},
@@ -747,9 +727,6 @@ func TestGitCommandUsingGpg(t *testing.T) {
func(string) (string, error) {
return "ON", nil
},
func(string) (string, error) {
return "", nil
},
func(gpgEnabled bool) {
assert.True(t, gpgEnabled)
},
@@ -759,9 +736,6 @@ func TestGitCommandUsingGpg(t *testing.T) {
func(string) (string, error) {
return "YeS", nil
},
func(string) (string, error) {
return "", nil
},
func(gpgEnabled bool) {
assert.True(t, gpgEnabled)
},
@@ -771,9 +745,6 @@ func TestGitCommandUsingGpg(t *testing.T) {
func(string) (string, error) {
return "1", nil
},
func(string) (string, error) {
return "", nil
},
func(gpgEnabled bool) {
assert.True(t, gpgEnabled)
},
@@ -783,8 +754,7 @@ func TestGitCommandUsingGpg(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
gitCmd.getLocalGitConfig = s.getLocalGitConfig
gitCmd.getGitConfigValue = s.getGitConfigValue
s.test(gitCmd.usingGpg())
})
}
@@ -793,11 +763,11 @@ func TestGitCommandUsingGpg(t *testing.T) {
// TestGitCommandCommit is a function.
func TestGitCommandCommit(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
getGlobalGitConfig func(string) (string, error)
test func(*exec.Cmd, error)
flags string
testName string
command func(string, ...string) *exec.Cmd
getGitConfigValue func(string) (string, error)
test func(*exec.Cmd, error)
flags string
}
scenarios := []scenario{
@@ -807,7 +777,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.EqualValues(t, "bash", cmd)
assert.EqualValues(t, []string{"-c", "git commit -m \"test\""}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(string) (string, error) {
return "true", nil
@@ -824,7 +794,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "-m", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(string) (string, error) {
return "false", nil
@@ -841,7 +811,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--no-verify", "-m", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(string) (string, error) {
return "false", nil
@@ -858,7 +828,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "-m", "test"}, args)
return exec.Command("test")
return secureexec.Command("test")
},
func(string) (string, error) {
return "false", nil
@@ -874,7 +844,7 @@ func TestGitCommandCommit(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
gitCmd.getGitConfigValue = s.getGitConfigValue
gitCmd.OSCommand.Command = s.command
s.test(gitCmd.Commit("test", s.flags))
})
@@ -884,10 +854,10 @@ func TestGitCommandCommit(t *testing.T) {
// TestGitCommandAmendHead is a function.
func TestGitCommandAmendHead(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
getGlobalGitConfig func(string) (string, error)
test func(*exec.Cmd, error)
testName string
command func(string, ...string) *exec.Cmd
getGitConfigValue func(string) (string, error)
test func(*exec.Cmd, error)
}
scenarios := []scenario{
@@ -897,7 +867,7 @@ func TestGitCommandAmendHead(t *testing.T) {
assert.EqualValues(t, "bash", cmd)
assert.EqualValues(t, []string{"-c", "git commit --amend --no-edit --allow-empty"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(string) (string, error) {
return "true", nil
@@ -913,7 +883,7 @@ func TestGitCommandAmendHead(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--amend", "--no-edit", "--allow-empty"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(string) (string, error) {
return "false", nil
@@ -929,7 +899,7 @@ func TestGitCommandAmendHead(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--amend", "--no-edit", "--allow-empty"}, args)
return exec.Command("test")
return secureexec.Command("test")
},
func(string) (string, error) {
return "false", nil
@@ -944,7 +914,7 @@ func TestGitCommandAmendHead(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
gitCmd.getGitConfigValue = s.getGitConfigValue
gitCmd.OSCommand.Command = s.command
s.test(gitCmd.AmendHead())
})
@@ -954,12 +924,11 @@ func TestGitCommandAmendHead(t *testing.T) {
// TestGitCommandPush is a function.
func TestGitCommandPush(t *testing.T) {
type scenario struct {
testName string
getLocalGitConfig func(string) (string, error)
getGlobalGitConfig func(string) (string, error)
command func(string, ...string) *exec.Cmd
forcePush bool
test func(error)
testName string
getGitConfigValue func(string) (string, error)
command func(string, ...string) *exec.Cmd
forcePush bool
test func(error)
}
scenarios := []scenario{
@@ -968,14 +937,11 @@ func TestGitCommandPush(t *testing.T) {
func(string) (string, error) {
return "", nil
},
func(string) (string, error) {
return "", nil
},
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push", "--follow-tags"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
false,
func(err error) {
@@ -987,14 +953,11 @@ func TestGitCommandPush(t *testing.T) {
func(string) (string, error) {
return "", nil
},
func(string) (string, error) {
return "", nil
},
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push", "--follow-tags", "--force-with-lease"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
true,
func(err error) {
@@ -1002,55 +965,30 @@ func TestGitCommandPush(t *testing.T) {
},
},
{
"Push with force disabled, follow-tags off locally",
"Push with force disabled, follow-tags off",
func(string) (string, error) {
return "false", nil
},
func(string) (string, error) {
return "", nil
},
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
false,
func(err error) {
assert.NoError(t, err)
},
},
{
"Push with force enabled, follow-tags off globally",
func(string) (string, error) {
return "", nil
},
func(string) (string, error) {
return "false", nil
},
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push", "--force-with-lease"}, args)
return exec.Command("echo")
},
true,
func(err error) {
assert.NoError(t, err)
},
},
{
"Push with an error occurring, follow-tags on",
func(string) (string, error) {
return "", nil
},
func(string) (string, error) {
return "", nil
},
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push", "--follow-tags"}, args)
return exec.Command("test")
return secureexec.Command("test")
},
false,
func(err error) {
@@ -1063,8 +1001,7 @@ func TestGitCommandPush(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.OSCommand.Command = s.command
gitCmd.getLocalGitConfig = s.getLocalGitConfig
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
gitCmd.getGitConfigValue = s.getGitConfigValue
err := gitCmd.Push("test", s.forcePush, "", "", func(passOrUname string) string {
return "\n"
})
@@ -1087,7 +1024,7 @@ func TestGitCommandCatFile(t *testing.T) {
assert.EqualValues(t, osCmd, cmd)
assert.EqualValues(t, []string{"test.txt"}, args)
return exec.Command("echo", "-n", "test")
return secureexec.Command("echo", "-n", "test")
}
o, err := gitCmd.CatFile("test.txt")
@@ -1100,9 +1037,9 @@ func TestGitCommandStageFile(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"add", "test.txt"}, args)
assert.EqualValues(t, []string{"add", "--", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.NoError(t, gitCmd.StageFile("test.txt"))
@@ -1122,9 +1059,9 @@ func TestGitCommandUnstageFile(t *testing.T) {
"Remove an untracked file from staging",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"rm", "--cached", "--force", "test.txt"}, args)
assert.EqualValues(t, []string{"rm", "--cached", "--force", "--", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -1135,9 +1072,9 @@ func TestGitCommandUnstageFile(t *testing.T) {
"Remove a tracked file from staging",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"reset", "HEAD", "test.txt"}, args)
assert.EqualValues(t, []string{"reset", "HEAD", "--", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -1173,7 +1110,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("test")
return secureexec.Command("test")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1198,7 +1135,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("test")
return secureexec.Command("test")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1221,7 +1158,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("test")
return secureexec.Command("test")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1247,7 +1184,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("echo")
return secureexec.Command("echo")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1273,7 +1210,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("echo")
return secureexec.Command("echo")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1300,7 +1237,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("echo")
return secureexec.Command("echo")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1327,7 +1264,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("echo")
return secureexec.Command("echo")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1354,7 +1291,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
return exec.Command("echo")
return secureexec.Command("echo")
}, &cmdsCalled
},
func(cmdsCalled *[][]string, err error) {
@@ -1400,7 +1337,7 @@ func TestGitCommandCheckout(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"checkout", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -1413,7 +1350,7 @@ func TestGitCommandCheckout(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"checkout", "--force", "test"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -1437,7 +1374,7 @@ func TestGitCommandGetBranchGraph(t *testing.T) {
gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
_, err := gitCmd.GetBranchGraph("test")
assert.NoError(t, err)
@@ -1448,7 +1385,7 @@ func TestGitCommandGetAllBranchGraph(t *testing.T) {
gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"log", "--graph", "--all", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
}
cmdStr := gitCmd.Config.GetUserConfig().Git.AllBranchesLogCmd
_, err := gitCmd.OSCommand.RunCommandWithOutput(cmdStr)
@@ -1472,7 +1409,7 @@ func TestGitCommandDiff(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
&models.File{
Name: "test.txt",
@@ -1488,7 +1425,7 @@ func TestGitCommandDiff(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--cached", "--", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
&models.File{
Name: "test.txt",
@@ -1504,7 +1441,7 @@ func TestGitCommandDiff(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=never", "--", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
&models.File{
Name: "test.txt",
@@ -1518,9 +1455,9 @@ func TestGitCommandDiff(t *testing.T) {
"File not tracked and file has no staged changes",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--no-index", "/dev/null", "test.txt"}, args)
assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, args)
return exec.Command("echo")
return secureexec.Command("echo")
},
&models.File{
Name: "test.txt",
@@ -1554,7 +1491,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
"says we are on the master branch if we are",
func(cmd string, args ...string) *exec.Cmd {
assert.Equal(t, "git", cmd)
return exec.Command("echo", "master")
return secureexec.Command("echo", "master")
},
func(name string, displayname string, err error) {
assert.NoError(t, err)
@@ -1570,10 +1507,10 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
switch args[0] {
case "symbolic-ref":
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
return exec.Command("test")
return secureexec.Command("test")
case "branch":
assert.EqualValues(t, []string{"branch", "--contains"}, args)
return exec.Command("echo", "* master")
return secureexec.Command("echo", "* master")
}
return nil
@@ -1592,10 +1529,10 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
switch args[0] {
case "symbolic-ref":
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
return exec.Command("test")
return secureexec.Command("test")
case "branch":
assert.EqualValues(t, []string{"branch", "--contains"}, args)
return exec.Command("echo", "* (HEAD detached at 123abcd)")
return secureexec.Command("echo", "* (HEAD detached at 123abcd)")
}
return nil
@@ -1610,7 +1547,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
"bubbles up error if there is one",
func(cmd string, args ...string) *exec.Cmd {
assert.Equal(t, "git", cmd)
return exec.Command("test")
return secureexec.Command("test")
},
func(name string, displayname string, err error) {
assert.Error(t, err)
@@ -1648,7 +1585,7 @@ func TestGitCommandApplyPatch(t *testing.T) {
assert.Equal(t, "test", string(content))
return exec.Command("echo", "done")
return secureexec.Command("echo", "done")
},
func(err error) {
assert.NoError(t, err)
@@ -1669,7 +1606,7 @@ func TestGitCommandApplyPatch(t *testing.T) {
assert.Equal(t, "test", string(content))
return exec.Command("test")
return secureexec.Command("test")
},
func(err error) {
assert.Error(t, err)
@@ -1789,7 +1726,7 @@ func TestGitCommandCheckoutFile(t *testing.T) {
func TestGitCommandDiscardOldFileChanges(t *testing.T) {
type scenario struct {
testName string
getLocalGitConfig func(string) (string, error)
getGitConfigValue func(string) (string, error)
commits []*models.Commit
commitIndex int
fileName string
@@ -1870,7 +1807,7 @@ func TestGitCommandDiscardOldFileChanges(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.Command = s.command
gitCmd.getLocalGitConfig = s.getLocalGitConfig
gitCmd.getGitConfigValue = s.getGitConfigValue
s.test(gitCmd.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
})
}
@@ -2163,18 +2100,18 @@ func TestFindDotGitDir(t *testing.T) {
// TestEditFile is a function.
func TestEditFile(t *testing.T) {
type scenario struct {
filename string
command func(string, ...string) *exec.Cmd
getenv func(string) string
getGlobalGitConfig func(string) (string, error)
test func(*exec.Cmd, error)
filename string
command func(string, ...string) *exec.Cmd
getenv func(string) string
getGitConfigValue func(string) (string, error)
test func(*exec.Cmd, error)
}
scenarios := []scenario{
{
"test",
func(name string, arg ...string) *exec.Cmd {
return exec.Command("exit", "1")
return secureexec.Command("exit", "1")
},
func(env string) string {
return ""
@@ -2190,7 +2127,7 @@ func TestEditFile(t *testing.T) {
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
return secureexec.Command("exit", "1")
}
assert.EqualValues(t, "nano", name)
@@ -2211,7 +2148,7 @@ func TestEditFile(t *testing.T) {
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
return secureexec.Command("exit", "1")
}
assert.EqualValues(t, "nano", name)
@@ -2236,7 +2173,7 @@ func TestEditFile(t *testing.T) {
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
return secureexec.Command("exit", "1")
}
assert.EqualValues(t, "emacs", name)
@@ -2261,7 +2198,7 @@ func TestEditFile(t *testing.T) {
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.EqualValues(t, "vi", name)
@@ -2282,7 +2219,7 @@ func TestEditFile(t *testing.T) {
"file/with space",
func(name string, args ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
return secureexec.Command("echo")
}
assert.EqualValues(t, "vi", name)
@@ -2306,7 +2243,7 @@ func TestEditFile(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.OSCommand.Command = s.command
gitCmd.OSCommand.Getenv = s.getenv
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
gitCmd.getGitConfigValue = s.getGitConfigValue
s.test(gitCmd.EditFile(s.filename))
}
}

56
pkg/commands/gitconfig.go Normal file
View File

@@ -0,0 +1,56 @@
package commands
import (
"bytes"
"fmt"
"io/ioutil"
"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
// Copyright (c) 2014 tcnksm
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
func getGitConfigValue(key string) (string, error) {
gitArgs := []string{"config", "--get", "--null", key}
var stdout bytes.Buffer
cmd := secureexec.Command("git", gitArgs...)
cmd.Stdout = &stdout
cmd.Stderr = ioutil.Discard
err := cmd.Run()
if exitError, ok := err.(*exec.ExitError); ok {
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
if waitStatus.ExitStatus() == 1 {
return "", fmt.Errorf("the key `%s` is not found", key)
}
}
return "", err
}
return strings.TrimRight(stdout.String(), "\000"), nil
}

View File

@@ -14,7 +14,7 @@ func (c *GitCommand) GetFilesInDiff(from string, to string, reverse bool, patchM
reverseFlag = " -R "
}
filenames, err := c.OSCommand.RunCommandWithOutput("git diff --submodule --no-ext-diff --name-status %s %s %s", reverseFlag, from, to)
filenames, err := c.OSCommand.RunCommandWithOutput("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)
if err != nil {
return nil, err
}
@@ -26,13 +26,12 @@ func (c *GitCommand) GetFilesInDiff(from string, to string, reverse bool, patchM
func (c *GitCommand) getCommitFilesFromFilenames(filenames string, parent string, patchManager *patch.PatchManager) []*models.CommitFile {
commitFiles := make([]*models.CommitFile, 0)
for _, line := range strings.Split(strings.TrimRight(filenames, "\n"), "\n") {
lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00")
n := len(lines)
for i := 0; i < n-1; i += 2 {
// typical result looks like 'A my_file' meaning my_file was added
if line == "" {
continue
}
changeStatus := line[0:1]
name := line[2:]
changeStatus := lines[i]
name := lines[i+1]
status := patch.UNSELECTED
if patchManager != nil && patchManager.To == parent {
status = patchManager.GetFileStatus(name)

View File

@@ -6,6 +6,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
)
@@ -39,10 +40,10 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
switch args[0] {
case "symbolic-ref":
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
return exec.Command("echo", "master")
return secureexec.Command("echo", "master")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
return exec.Command("test")
return secureexec.Command("test")
}
return nil
},
@@ -59,10 +60,10 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
switch args[0] {
case "symbolic-ref":
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
return exec.Command("echo", "master")
return secureexec.Command("echo", "master")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
return exec.Command("echo", "blah")
return secureexec.Command("echo", "blah")
}
return nil
},
@@ -79,10 +80,10 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
switch args[0] {
case "symbolic-ref":
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
return exec.Command("echo", "feature/test")
return secureexec.Command("echo", "feature/test")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args)
return exec.Command("echo", "blah")
return secureexec.Command("echo", "blah")
}
return nil
},
@@ -94,7 +95,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
{
"bubbles up error if there is one",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("test")
return secureexec.Command("test")
},
func(output string, err error) {
assert.Error(t, err)

View File

@@ -37,7 +37,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
change := statusString[0:2]
stagedChange := change[0:1]
unstagedChange := statusString[1:2]
filename := c.OSCommand.Unquote(statusString[3:])
filename := statusString[3:]
untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change)
hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange)
hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change)
@@ -72,7 +72,24 @@ func (c *GitCommand) GitStatus(opts GitStatusOptions) (string, error) {
noRenamesFlag = "--no-renames"
}
return c.OSCommand.RunCommandWithOutput("git status %s --porcelain %s", opts.UntrackedFilesArg, noRenamesFlag)
statusLines, err := c.OSCommand.RunCommandWithOutput("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag)
if err != nil {
return "", err
}
splitLines := strings.Split(statusLines, "\x00")
// if a line starts with 'R' then the next line is the original file.
for i := 0; i < len(splitLines)-1; i++ {
original := splitLines[i]
if strings.HasPrefix(original, "R ") {
next := splitLines[i+1]
updated := "R " + next + models.RENAME_SEPARATOR + strings.TrimPrefix(original, "R ")
splitLines[i] = updated
splitLines = append(splitLines[0:i+1], splitLines[i+2:]...)
}
}
return strings.Join(splitLines, "\n"), nil
}
// MergeStatusFiles merge status files

View File

@@ -8,7 +8,6 @@ import (
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
@@ -16,6 +15,7 @@ import (
"github.com/atotto/clipboard"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
"github.com/sirupsen/logrus"
@@ -23,14 +23,13 @@ import (
// Platform stores the os state
type Platform struct {
OS string
CatCmd string
Shell string
ShellArg string
EscapedQuote string
OpenCommand string
OpenLinkCommand string
FallbackEscapedQuote string
OS string
CatCmd string
Shell string
ShellArg string
EscapedQuote string
OpenCommand string
OpenLinkCommand string
}
// OSCommand holds all the os commands
@@ -49,7 +48,7 @@ func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
Log: log,
Platform: getPlatform(),
Config: config,
Command: exec.Command,
Command: secureexec.Command,
BeforeExecuteCmd: func(*exec.Cmd) {},
Getenv: os.Getenv,
}
@@ -128,7 +127,7 @@ func (c *OSCommand) ShellCommandFromString(commandStr string) *exec.Cmd {
if c.Platform.OS == "windows" {
quotedCommand = commandStr
} else {
quotedCommand = strconv.Quote(commandStr)
quotedCommand = c.Quote(commandStr)
}
shellCommand := fmt.Sprintf("%s %s %s", c.Platform.Shell, c.Platform.ShellArg, quotedCommand)
@@ -244,20 +243,19 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *ex
// Quote wraps a message in platform-specific quotation marks
func (c *OSCommand) Quote(message string) string {
message = strings.Replace(message, "`", "\\`", -1)
escapedQuote := c.Platform.EscapedQuote
if strings.Contains(message, c.Platform.EscapedQuote) {
escapedQuote = c.Platform.FallbackEscapedQuote
if c.Platform.OS == "windows" {
message = strings.Replace(message, `"`, `"'"'"`, -1)
message = strings.Replace(message, `\"`, `\\"`, -1)
} else {
message = strings.Replace(message, `\`, `\\`, -1)
message = strings.Replace(message, `"`, `\"`, -1)
message = strings.Replace(message, "`", "\\`", -1)
message = strings.Replace(message, "$", "\\$", -1)
}
escapedQuote := c.Platform.EscapedQuote
return escapedQuote + message + escapedQuote
}
// Unquote removes wrapping quotations marks if they are present
// this is needed for removing quotes from staged filenames with spaces
func (c *OSCommand) Unquote(message string) string {
return strings.Replace(message, `"`, "", -1)
}
// AppendLineToFile adds a new line in file
func (c *OSCommand) AppendLineToFile(filename, line string) error {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)

View File

@@ -8,13 +8,12 @@ import (
func getPlatform() *Platform {
return &Platform{
OS: runtime.GOOS,
CatCmd: "cat",
Shell: "bash",
ShellArg: "-c",
EscapedQuote: "'",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
FallbackEscapedQuote: "\"",
OS: runtime.GOOS,
CatCmd: "cat",
Shell: "bash",
ShellArg: "-c",
EscapedQuote: `"`,
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}
}

View File

@@ -6,6 +6,7 @@ import (
"os/exec"
"testing"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/stretchr/testify/assert"
)
@@ -70,7 +71,7 @@ func TestOSCommandOpenFile(t *testing.T) {
{
"test",
func(name string, arg ...string) *exec.Cmd {
return exec.Command("exit", "1")
return secureexec.Command("exit", "1")
},
func(err error) {
assert.Error(t, err)
@@ -81,7 +82,7 @@ func TestOSCommandOpenFile(t *testing.T) {
func(name string, arg ...string) *exec.Cmd {
assert.Equal(t, "open", name)
assert.Equal(t, []string{"test"}, arg)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -92,7 +93,7 @@ func TestOSCommandOpenFile(t *testing.T) {
func(name string, arg ...string) *exec.Cmd {
assert.Equal(t, "open", name)
assert.Equal(t, []string{"filename with spaces"}, arg)
return exec.Command("echo")
return secureexec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
@@ -113,6 +114,8 @@ func TestOSCommandOpenFile(t *testing.T) {
func TestOSCommandQuote(t *testing.T) {
osCommand := NewDummyOSCommand()
osCommand.Platform.OS = "linux"
actual := osCommand.Quote("hello `test`")
expected := osCommand.Platform.EscapedQuote + "hello \\`test\\`" + osCommand.Platform.EscapedQuote
@@ -128,7 +131,7 @@ func TestOSCommandQuoteSingleQuote(t *testing.T) {
actual := osCommand.Quote("hello 'test'")
expected := osCommand.Platform.FallbackEscapedQuote + "hello 'test'" + osCommand.Platform.FallbackEscapedQuote
expected := osCommand.Platform.EscapedQuote + "hello 'test'" + osCommand.Platform.EscapedQuote
assert.EqualValues(t, expected, actual)
}
@@ -141,18 +144,20 @@ func TestOSCommandQuoteDoubleQuote(t *testing.T) {
actual := osCommand.Quote(`hello "test"`)
expected := osCommand.Platform.EscapedQuote + "hello \"test\"" + osCommand.Platform.EscapedQuote
expected := osCommand.Platform.EscapedQuote + `hello \"test\"` + osCommand.Platform.EscapedQuote
assert.EqualValues(t, expected, actual)
}
// TestOSCommandUnquote is a function.
func TestOSCommandUnquote(t *testing.T) {
// TestOSCommandQuoteWindows tests the quote function for Windows
func TestOSCommandQuoteWindows(t *testing.T) {
osCommand := NewDummyOSCommand()
actual := osCommand.Unquote(`hello "test"`)
osCommand.Platform.OS = "windows"
expected := "hello test"
actual := osCommand.Quote(`hello "test"`)
expected := osCommand.Platform.EscapedQuote + `hello "'"'"test"'"'"` + osCommand.Platform.EscapedQuote
assert.EqualValues(t, expected, actual)
}

View File

@@ -2,11 +2,10 @@ package oscommands
func getPlatform() *Platform {
return &Platform{
OS: "windows",
CatCmd: "type",
Shell: "cmd",
ShellArg: "/c",
EscapedQuote: `\"`,
FallbackEscapedQuote: "\\'",
OS: "windows",
CatCmd: "cmd /c type",
Shell: "cmd",
ShellArg: "/c",
EscapedQuote: `\"`,
}
}

View File

@@ -6,6 +6,7 @@ import (
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/stretchr/testify/assert"
)
@@ -63,12 +64,12 @@ func TestCreatePullRequest(t *testing.T) {
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call
if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "git@bitbucket.org:johndoe/social_network.git")
return secureexec.Command("echo", "git@bitbucket.org:johndoe/social_network.git")
}
assert.Equal(t, cmd, "open")
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"})
return exec.Command("echo")
return secureexec.Command("echo")
},
test: func(err error) {
assert.NoError(t, err)
@@ -83,12 +84,12 @@ func TestCreatePullRequest(t *testing.T) {
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call
if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git")
return secureexec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git")
}
assert.Equal(t, cmd, "open")
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1"})
return exec.Command("echo")
return secureexec.Command("echo")
},
test: func(err error) {
assert.NoError(t, err)
@@ -103,12 +104,12 @@ func TestCreatePullRequest(t *testing.T) {
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call
if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "git@github.com:peter/calculator.git")
return secureexec.Command("echo", "git@github.com:peter/calculator.git")
}
assert.Equal(t, cmd, "open")
assert.Equal(t, args, []string{"https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"})
return exec.Command("echo")
return secureexec.Command("echo")
},
test: func(err error) {
assert.NoError(t, err)
@@ -123,12 +124,12 @@ func TestCreatePullRequest(t *testing.T) {
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call
if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "git@gitlab.com:peter/calculator.git")
return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git")
}
assert.Equal(t, cmd, "open")
assert.Equal(t, args, []string{"https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"})
return exec.Command("echo")
return secureexec.Command("echo")
},
test: func(err error) {
assert.NoError(t, err)
@@ -141,7 +142,7 @@ func TestCreatePullRequest(t *testing.T) {
},
remoteUrl: "git@something.com:peter/calculator.git",
command: func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo")
return secureexec.Command("echo")
},
test: func(err error) {
assert.Error(t, err)
@@ -161,14 +162,10 @@ func TestCreatePullRequest(t *testing.T) {
"invalid.work.com": "noservice:invalid.work.com",
"noservice.work.com": "noservice.work.com",
}
gitCommand.getLocalGitConfig = func(path string) (string, error) {
gitCommand.getGitConfigValue = func(path string) (string, error) {
assert.Equal(t, path, "remote.origin.url")
return s.remoteUrl, nil
}
gitCommand.getGlobalGitConfig = func(path string) (string, error) {
assert.Equal(t, path, "remote.origin.url")
return "", nil
}
dummyPullRequest := NewPullRequest(gitCommand)
s.test(dummyPullRequest.Create(s.branch))
})

View File

@@ -41,6 +41,7 @@ type AppConfigurer interface {
SaveAppState() error
SetIsNewRepo(bool)
GetIsNewRepo() bool
ReloadUserConfig() error
}
// NewAppConfig makes a new app config
@@ -203,6 +204,16 @@ func (c *AppConfig) GetUserConfigDir() string {
return c.UserConfigDir
}
func (c *AppConfig) ReloadUserConfig() error {
userConfig, err := loadUserConfigWithDefaults(c.UserConfigDir)
if err != nil {
return err
}
c.UserConfig = userConfig
return nil
}
func configFilePath(filename string) (string, error) {
folder, err := findOrCreateConfigDir()
if err != nil {

View File

@@ -4,6 +4,7 @@ type UserConfig struct {
Gui GuiConfig `yaml:"gui"`
Git GitConfig `yaml:"git"`
Update UpdateConfig `yaml:"update"`
Refresher RefresherConfig `yaml:"refresher"`
Reporting string `yaml:"reporting"`
SplashUpdatesIndex int `yaml:"splashUpdatesIndex"`
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
@@ -17,6 +18,11 @@ type UserConfig struct {
NotARepository string `yaml:"notARepository"`
}
type RefresherConfig struct {
RefreshInterval int `yaml:"refreshInterval"`
FetchInterval int `yaml:"fetchInterval"`
}
type GuiConfig struct {
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
@@ -306,6 +312,10 @@ func GetDefaultConfig() *UserConfig {
DisableForcePushing: false,
CommitPrefixes: map[string]CommitPrefixConfig(nil),
},
Refresher: RefresherConfig{
RefreshInterval: 10,
FetchInterval: 60,
},
Update: UpdateConfig{
Method: "prompt",
Days: 14,

View File

@@ -434,7 +434,8 @@ func (gui *Gui) handleRenameBranch(g *gocui.Gui, v *gocui.View) error {
promptForNewName := func() error {
return gui.prompt(promptOpts{
title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
initialContent: branch.Name,
handleConfirm: func(newBranchName string) error {
if err := gui.GitCommand.RenameBranch(branch.Name, newBranchName); err != nil {
return gui.surfaceError(err)
@@ -497,7 +498,7 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
title: message,
initialContent: prefilledName,
handleConfirm: func(response string) error {
if err := gui.GitCommand.NewBranch(response, item.ID()); err != nil {
if err := gui.GitCommand.NewBranch(sanitizedBranchName(response), item.ID()); err != nil {
return err
}
@@ -533,7 +534,7 @@ func (gui *Gui) getBranchNames() []string {
func (gui *Gui) findBranchNameSuggestions(input string) []*types.Suggestion {
branchNames := gui.getBranchNames()
matchingBranchNames := utils.FuzzySearch(input, branchNames)
matchingBranchNames := utils.FuzzySearch(sanitizedBranchName(input), branchNames)
suggestions := make([]*types.Suggestion, len(matchingBranchNames))
for i, branchName := range matchingBranchNames {
@@ -545,3 +546,9 @@ func (gui *Gui) findBranchNameSuggestions(input string) []*types.Suggestion {
return suggestions
}
// sanitizedBranchName will remove all spaces in favor of a dash "-" to meet
// git's branch naming requirement.
func sanitizedBranchName(input string) string {
return strings.Replace(input, " ", "-", -1)
}

View File

@@ -196,7 +196,7 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
return gui.surfaceError(err)
}
truncatedItemId := utils.TruncateWithEllipsis(strings.ReplaceAll(itemId, "\n", " "), 50)
truncatedItemId := utils.TruncateWithEllipsis(strings.Replace(itemId, "\n", " ", -1), 50)
gui.raiseToast(fmt.Sprintf("'%s' %s", truncatedItemId, gui.Tr.LcCopiedToClipboard))

View File

@@ -464,6 +464,9 @@ func (gui *Gui) Run() error {
}
g.OnSearchEscape = gui.onSearchEscape
if err := gui.Config.ReloadUserConfig(); err != nil {
return nil
}
userConfig := gui.Config.GetUserConfig()
g.SearchEscapeKey = gui.getKey(userConfig.Keybinding.Universal.Return)
g.NextSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.NextMatch)
@@ -495,7 +498,7 @@ func (gui *Gui) Run() error {
go utils.Safe(gui.startBackgroundFetch)
}
gui.goEvery(time.Second*10, gui.stopChan, gui.refreshFilesAndSubmodules)
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.RefreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
@@ -643,8 +646,9 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun
func (gui *Gui) startBackgroundFetch() {
gui.waitForIntro.Wait()
isNew := gui.Config.GetIsNewRepo()
userConfig := gui.Config.GetUserConfig()
if !isNew {
time.After(60 * time.Second)
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
}
err := gui.fetch(false)
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
@@ -653,7 +657,7 @@ func (gui *Gui) startBackgroundFetch() {
prompt: gui.Tr.NoAutomaticGitFetchBody,
})
} else {
gui.goEvery(time.Second*60, gui.stopChan, func() error {
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
err := gui.fetch(false)
return err
})

View File

@@ -6,7 +6,6 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
@@ -14,6 +13,7 @@ import (
"github.com/creack/pty"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/stretchr/testify/assert"
)
@@ -250,7 +250,7 @@ func Test(t *testing.T) {
func createFixture(testPath, actualDir string) error {
osCommand := oscommands.NewDummyOSCommand()
bashScriptPath := filepath.Join(testPath, "setup.sh")
cmd := exec.Command("bash", bashScriptPath, actualDir)
cmd := secureexec.Command("bash", bashScriptPath, actualDir)
if err := osCommand.RunExecutable(cmd); err != nil {
return err

View File

@@ -22,6 +22,7 @@ type Binding struct {
Description string
Alternative string
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
OpensMenu bool
}
// GetDisplayStrings returns the display string of a file
@@ -268,12 +269,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Universal.CreateRebaseOptionsMenu),
Handler: gui.wrappedHandler(gui.handleCreateRebaseOptionsMenu),
Description: gui.Tr.ViewMergeRebaseOptions,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.CreatePatchOptionsMenu),
Handler: gui.handleCreatePatchOptionsMenu,
Description: gui.Tr.ViewPatchOptions,
OpensMenu: true,
},
{
ViewName: "",
@@ -298,6 +301,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Universal.OptionMenu),
Handler: gui.handleCreateOptionsMenu,
Description: gui.Tr.LcOpenMenu,
OpensMenu: true,
},
{
ViewName: "",
@@ -406,6 +410,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleCreateDiscardMenu,
Description: gui.Tr.LcViewDiscardOptions,
OpensMenu: true,
},
{
ViewName: "files",
@@ -448,6 +453,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Files.ViewStashOptions),
Handler: gui.handleCreateStashMenu,
Description: gui.Tr.LcViewStashOptions,
OpensMenu: true,
},
{
ViewName: "files",
@@ -462,6 +468,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Files.ViewResetOptions),
Handler: gui.handleCreateResetMenu,
Description: gui.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "files",
@@ -496,6 +503,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateResetToUpstreamMenu,
Description: gui.Tr.LcViewResetToUpstreamOptions,
OpensMenu: true,
},
{
ViewName: "branches",
@@ -566,6 +574,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Branches.ViewGitFlowOptions),
Handler: gui.handleCreateGitFlowMenu,
Description: gui.Tr.LcGitFlowOptions,
OpensMenu: true,
},
{
ViewName: "branches",
@@ -580,6 +589,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateResetToBranchMenu,
Description: gui.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "branches",
@@ -636,6 +646,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateResetToTagMenu,
Description: gui.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "branches",
@@ -657,6 +668,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateResetToRemoteBranchMenu,
Description: gui.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "branches",
@@ -861,6 +873,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateReflogResetMenu,
Description: gui.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "commits",
@@ -916,6 +929,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.wrappedHandler(gui.handleCreateSubCommitResetMenu),
Description: gui.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "branches",
@@ -1059,18 +1073,21 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Universal.FilteringMenu),
Handler: gui.handleCreateFilteringMenuPanel,
Description: gui.Tr.LcOpenScopingMenu,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.DiffingMenu),
Handler: gui.handleCreateDiffingMenuPanel,
Description: gui.Tr.LcOpenDiffingMenu,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.DiffingMenuAlt),
Handler: gui.handleCreateDiffingMenuPanel,
Description: gui.Tr.LcOpenDiffingMenu,
OpensMenu: true,
},
{
ViewName: "secondary",
@@ -1618,6 +1635,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Universal.Remove),
Handler: gui.forSubmodule(gui.handleResetRemoveSubmodule),
Description: gui.Tr.LcViewResetAndRemoveOptions,
OpensMenu: true,
},
{
ViewName: "files",
@@ -1653,6 +1671,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gui.getKey(config.Submodules.BulkMenu),
Handler: gui.wrappedHandler(gui.handleBulkSubmoduleActionsMenu),
Description: gui.Tr.LcViewBulkSubmoduleOptions,
OpensMenu: true,
},
}

View File

@@ -3,6 +3,7 @@ package gui
import (
"strings"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -33,6 +34,17 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
return append(bindingsPanel, bindingsGlobal...)
}
func (gui *Gui) displayDescription(binding *Binding) string {
commandColor := color.New(color.FgCyan)
menuColor := color.New(color.FgMagenta)
if binding.OpensMenu {
return menuColor.Sprintf("%s...", binding.Description)
}
return commandColor.Sprint(binding.Description)
}
func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
bindings := gui.getBindings(v)
@@ -41,7 +53,7 @@ func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
for i, binding := range bindings {
binding := binding // note to self, never close over loop variables
menuItems[i] = &menuItem{
displayStrings: []string{GetKeyDisplay(binding.Key), binding.Description},
displayStrings: []string{GetKeyDisplay(binding.Key), gui.displayDescription(binding)},
onPress: func() error {
if binding.Key == nil {
return nil

View File

@@ -303,8 +303,8 @@ func dutchTranslationSet() TranslationSet {
LcAddNewRemote: `voeg een nieuwe remote toe`,
LcNewRemoteName: `Nieuwe remote name:`,
LcNewRemoteUrl: `Nieuwe remote url:`,
LcEditRemoteName: `Enter updated remote naam voor {{ .remoteName }}:`,
LcEditRemoteUrl: `Enter updated remote url voor {{ .remoteName }}:`,
LcEditRemoteName: `Enter updated remote naam voor {{.remoteName}}:`,
LcEditRemoteUrl: `Enter updated remote url voor {{.remoteName}}:`,
LcRemoveRemote: `verwijder remote`,
LcRemoveRemotePrompt: "Weet je zeker dat je deze remote wilt verwijderen",
DeleteRemoteBranch: "Verwijder Remote Branch",

View File

@@ -430,13 +430,41 @@ type TranslationSet struct {
SubCommitsTitle string
SubmodulesTitle string
NavigationTitle string
SuggestionsTitle string
PushingTagStatus string
PullRequestURLCopiedToClipboard string
CommitMessageCopiedToClipboard string
LcCopiedToClipboard string
}
const englishReleaseNotes = `## lazygit 0.24 Release Notes
const englishReleaseNotes = `## lazygit 0.26 Release Notes
- Config changes applied after editing from within lazygit, no reload required.
- LOTS of fixes for rendering filenames with strange characters, escaped
characters, and UI fixes, by the amazing @Ryooooooga!
- Also thanks to @Isti115
## lazygit 0.25 Release Notes
- Fixes for windows, thanks @murphy66!
- Allow mapping spaces to dashes when creating a branch, thanks @caquillo07!
- Allow configuring file refresh and fetch frequency, thanks @Liberatys!
- Minor security improvement
- Wide characters supported when entering commit messages, thanks @Ryooooooga!
- Original branch name appears when renaming, thanks piresrui!
- Better menus, thanks @1jz!
- Also thanks to @snipem, @dbast, and @dawidd6
## lazygit 0.24 Release Notes
- Suggestions now shown when checking out branch by name
@@ -843,8 +871,8 @@ func englishTranslationSet() TranslationSet {
LcAddNewRemote: `add new remote`,
LcNewRemoteName: `New remote name:`,
LcNewRemoteUrl: `New remote url:`,
LcEditRemoteName: `Enter updated remote name for {{ .remoteName }}:`,
LcEditRemoteUrl: `Enter updated remote url for {{ .remoteName }}:`,
LcEditRemoteName: `Enter updated remote name for {{.remoteName}}:`,
LcEditRemoteUrl: `Enter updated remote url for {{.remoteName}}:`,
LcRemoveRemote: `remove remote`,
LcRemoveRemotePrompt: "Are you sure you want to remove remote",
DeleteRemoteBranch: "Delete Remote Branch",
@@ -960,6 +988,7 @@ func englishTranslationSet() TranslationSet {
SubCommitsTitle: "Sub-commits",
SubmodulesTitle: "Submodules",
NavigationTitle: "List Panel Navigation",
SuggestionsTitle: "Suggestions",
PushingTagStatus: "pushing tag",
PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
CommitMessageCopiedToClipboard: "Commit message copied to clipboard",

View File

@@ -0,0 +1,11 @@
// +build !windows
package secureexec
import (
"os/exec"
)
func Command(name string, args ...string) *exec.Cmd {
return exec.Command(name, args...)
}

View File

@@ -0,0 +1,30 @@
// +build windows
package secureexec
import (
"os/exec"
"github.com/cli/safeexec"
)
// calling exec.Command directly on a windows machine poses a security risk due to
// the current directory being searched first before any directories in the PATH
// variable, meaning you might clone a repo that contains a program called 'git'
// which does something malicious when executed.
// see https://github.com/golang/go/issues/38736 for more context. We'll likely
// be able to just throw out this code and switch to the official solution when it exists.
// I consider this a minor security concern because you're just as vulnerable if
// you call `git status` from the command line directly but no harm in playing it
// safe.
func Command(name string, args ...string) *exec.Cmd {
bin, err := safeexec.LookPath(name)
if err != nil {
bin = name
}
return exec.Command(bin, args...)
}

View File

@@ -2,11 +2,11 @@ package test
import (
"os"
"os/exec"
"path/filepath"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -24,7 +24,7 @@ func GenerateRepo(filename string) error {
if err := os.Chdir(testPath); err != nil {
return err
}
if output, err := exec.Command("bash", filename).CombinedOutput(); err != nil {
if output, err := secureexec.Command("bash", filename).CombinedOutput(); err != nil {
return errors.New(string(output))
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"testing"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/mgutz/str"
"github.com/stretchr/testify/assert"
)
@@ -27,7 +28,7 @@ func (i *CommandSwapper) SwapCommand(t *testing.T, cmd string, args []string) *e
}
splitCmd = str.ToArgv(i.Replace)
return exec.Command(splitCmd[0], splitCmd[1:]...)
return secureexec.Command(splitCmd[0], splitCmd[1:]...)
}
// CreateMockCommand creates a command function that will verify its receiving the right sequence of commands from lazygit

View File

@@ -81,6 +81,7 @@ func localisedTitle(mApp *app.App, str string) string {
"search": tr.SearchTitle,
"secondary": tr.SecondaryTitle,
"stash": tr.StashTitle,
"suggestions": tr.SuggestionsTitle,
}
title, ok := contextTitleMap[str]

View File

@@ -11,9 +11,10 @@ import (
"io/ioutil"
"log"
"os"
"os/exec"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/secureexec"
)
func main() {
@@ -49,7 +50,7 @@ func main() {
func runCommand(args ...string) {
fmt.Println(strings.Join(args, " "))
cmd := exec.Command(args[0], args[1:]...)
cmd := secureexec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()

25
vendor/github.com/cli/safeexec/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2020, GitHub Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

40
vendor/github.com/cli/safeexec/README.md generated vendored Normal file
View File

@@ -0,0 +1,40 @@
# safeexec
A Go module that provides a safer alternative to `exec.LookPath()` on Windows.
The following, relatively common approach to running external commands has a subtle vulnerability on Windows:
```go
import "os/exec"
func gitStatus() error {
// On Windows, this will result in `.\git.exe` or `.\git.bat` being executed
// if either were found in the current working directory.
cmd := exec.Command("git", "status")
return cmd.Run()
}
```
Searching the current directory (surprising behavior) before searching folders listed in the PATH environment variable (expected behavior) seems to be intended in Go and unlikely to be changed: https://github.com/golang/go/issues/38736
Since Go does not provide a version of [`exec.LookPath()`](https://golang.org/pkg/os/exec/#LookPath) that only searches PATH and does not search the current working directory, this module provides a `LookPath` function that works consistently across platforms.
Example use:
```go
import (
"os/exec"
"github.com/cli/safeexec"
)
func gitStatus() error {
gitBin, err := safeexec.LookPath("git")
if err != nil {
return err
}
cmd := exec.Command(gitBin, "status")
return cmd.Run()
}
```
## TODO
Ideally, this module would also provide `exec.Command()` and `exec.CommandContext()` equivalents that delegate to the patched version of `LookPath`. However, this doesn't seem possible since `LookPath` may return an error, while `exec.Command/CommandContext()` themselves do not return an error. In the standard library, the resulting `exec.Cmd` struct stores the LookPath error in a private field, but that functionality isn't available to us.

3
vendor/github.com/cli/safeexec/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/cli/safeexec
go 1.15

9
vendor/github.com/cli/safeexec/lookpath.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// +build !windows
package safeexec
import "os/exec"
func LookPath(file string) (string, error) {
return exec.LookPath(file)
}

120
vendor/github.com/cli/safeexec/lookpath_windows.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Package safeexec provides alternatives for exec package functions to avoid
// accidentally executing binaries found in the current working directory on
// Windows.
package safeexec
import (
"os"
"os/exec"
"path/filepath"
"strings"
)
func chkStat(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if d.IsDir() {
return os.ErrPermission
}
return nil
}
func hasExt(file string) bool {
i := strings.LastIndex(file, ".")
if i < 0 {
return false
}
return strings.LastIndexAny(file, `:\/`) < i
}
func findExecutable(file string, exts []string) (string, error) {
if len(exts) == 0 {
return file, chkStat(file)
}
if hasExt(file) {
if chkStat(file) == nil {
return file, nil
}
}
for _, e := range exts {
if f := file + e; chkStat(f) == nil {
return f, nil
}
}
return "", os.ErrNotExist
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// LookPath also uses PATHEXT environment variable to match
// a suitable candidate.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
var exts []string
x := os.Getenv(`PATHEXT`)
if x != "" {
for _, e := range strings.Split(strings.ToLower(x), `;`) {
if e == "" {
continue
}
if e[0] != '.' {
e = "." + e
}
exts = append(exts, e)
}
} else {
exts = []string{".com", ".exe", ".bat", ".cmd"}
}
if strings.ContainsAny(file, `:\/`) {
if f, err := findExecutable(file, exts); err == nil {
return f, nil
} else {
return "", &exec.Error{file, err}
}
}
// https://github.com/golang/go/issues/38736
// if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
// return f, nil
// }
path := os.Getenv("path")
for _, dir := range filepath.SplitList(path) {
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
return f, nil
}
}
return "", &exec.Error{file, exec.ErrNotFound}
}

View File

@@ -343,17 +343,18 @@ func (v *View) writeRune(x, y int, ch rune) error {
}
olen := len(v.lines[y])
w := runewidth.RuneWidth(ch)
var s []cell
if x >= len(v.lines[y]) {
s = make([]cell, x-len(v.lines[y])+1)
s = make([]cell, x-len(v.lines[y])+w)
} else if !v.Overwrite {
s = make([]cell, 1)
s = make([]cell, w)
}
v.lines[y] = append(v.lines[y], s...)
if !v.Overwrite || (v.Overwrite && x >= olen-1) {
copy(v.lines[y][x+1:], v.lines[y][x:])
if !v.Overwrite || (v.Overwrite && x >= olen-w) {
copy(v.lines[y][x+w:], v.lines[y][x:])
}
v.lines[y][x] = cell{
fgColor: v.FgColor,
@@ -361,6 +362,14 @@ func (v *View) writeRune(x, y int, ch rune) error {
chr: ch,
}
for i := 1; i < w; i++ {
v.lines[y][x+i] = cell{
fgColor: v.FgColor,
bgColor: v.BgColor,
chr: '\x00',
}
}
return nil
}
@@ -384,7 +393,7 @@ func (v *View) deleteRune(x, y int) (int, error) {
w := runewidth.RuneWidth(v.lines[y][i].chr)
tw += w
if tw > x {
v.lines[y] = append(v.lines[y][:i], v.lines[y][i+1:]...)
v.lines[y] = append(v.lines[y][:i], v.lines[y][i+w:]...)
return w, nil
}

View File

@@ -1,3 +1,5 @@
module github.com/mattn/go-runewidth
go 1.9
require github.com/rivo/uniseg v0.1.0

2
vendor/github.com/mattn/go-runewidth/go.sum generated vendored Normal file
View File

@@ -0,0 +1,2 @@
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=

View File

@@ -2,6 +2,8 @@ package runewidth
import (
"os"
"github.com/rivo/uniseg"
)
//go:generate go run script/generate.go
@@ -10,9 +12,6 @@ var (
// EastAsianWidth will be set true if the current locale is CJK
EastAsianWidth bool
// ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
ZeroWidthJoiner bool
// DefaultCondition is a condition in current locale
DefaultCondition = &Condition{}
)
@@ -30,7 +29,6 @@ func handleEnv() {
}
// update DefaultCondition
DefaultCondition.EastAsianWidth = EastAsianWidth
DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
}
type interval struct {
@@ -85,15 +83,13 @@ var nonprint = table{
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
type Condition struct {
EastAsianWidth bool
ZeroWidthJoiner bool
EastAsianWidth bool
}
// NewCondition return new instance of Condition which is current locale.
func NewCondition() *Condition {
return &Condition{
EastAsianWidth: EastAsianWidth,
ZeroWidthJoiner: ZeroWidthJoiner,
EastAsianWidth: EastAsianWidth,
}
}
@@ -110,38 +106,20 @@ func (c *Condition) RuneWidth(r rune) int {
}
}
func (c *Condition) stringWidth(s string) (width int) {
for _, r := range []rune(s) {
width += c.RuneWidth(r)
}
return width
}
func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
r1, r2 := rune(0), rune(0)
for _, r := range []rune(s) {
if r == 0xFE0E || r == 0xFE0F {
continue
}
w := c.RuneWidth(r)
if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
if width < w {
width = w
}
} else {
width += w
}
r1, r2 = r2, r
}
return width
}
// StringWidth return width as you can see
func (c *Condition) StringWidth(s string) (width int) {
if c.ZeroWidthJoiner {
return c.stringWidthZeroJoiner(s)
g := uniseg.NewGraphemes(s)
for g.Next() {
var chWidth int
for _, r := range g.Runes() {
chWidth = c.RuneWidth(r)
if chWidth > 0 {
break // Our best guess at this point is to use the width of the first non-zero-width rune.
}
}
width += chWidth
}
return c.stringWidth(s)
return
}
// Truncate return string truncated with w cells
@@ -149,19 +127,25 @@ func (c *Condition) Truncate(s string, w int, tail string) string {
if c.StringWidth(s) <= w {
return s
}
r := []rune(s)
tw := c.StringWidth(tail)
w -= tw
width := 0
i := 0
for ; i < len(r); i++ {
cw := c.RuneWidth(r[i])
if width+cw > w {
w -= c.StringWidth(tail)
var width int
pos := len(s)
g := uniseg.NewGraphemes(s)
for g.Next() {
var chWidth int
for _, r := range g.Runes() {
chWidth = c.RuneWidth(r)
if chWidth > 0 {
break // See StringWidth() for details.
}
}
if width+chWidth > w {
pos, _ = g.Positions()
break
}
width += cw
width += chWidth
}
return string(r[0:i]) + tail
return s[:pos] + tail
}
// Wrap return string wrapped with w cells
@@ -169,7 +153,7 @@ func (c *Condition) Wrap(s string, w int) string {
width := 0
out := ""
for _, r := range []rune(s) {
cw := RuneWidth(r)
cw := c.RuneWidth(r)
if r == '\n' {
out += string(r)
width = 0

21
vendor/github.com/rivo/uniseg/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Oliver Kuederle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

62
vendor/github.com/rivo/uniseg/README.md generated vendored Normal file
View File

@@ -0,0 +1,62 @@
# Unicode Text Segmentation for Go
[![Godoc Reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/rivo/uniseg)
[![Go Report](https://img.shields.io/badge/go%20report-A%2B-brightgreen.svg)](https://goreportcard.com/report/github.com/rivo/uniseg)
This Go package implements Unicode Text Segmentation according to [Unicode Standard Annex #29](http://unicode.org/reports/tr29/) (Unicode version 12.0.0).
At this point, only the determination of grapheme cluster boundaries is implemented.
## Background
In Go, [strings are read-only slices of bytes](https://blog.golang.org/strings). They can be turned into Unicode code points using the `for` loop or by casting: `[]rune(str)`. However, multiple code points may be combined into one user-perceived character or what the Unicode specification calls "grapheme cluster". Here are some examples:
|String|Bytes (UTF-8)|Code points (runes)|Grapheme clusters|
|-|-|-|-|
|Käse|6 bytes: `4b 61 cc 88 73 65`|5 code points: `4b 61 308 73 65`|4 clusters: `[4b],[61 308],[73],[65]`|
|🏳️‍🌈|14 bytes: `f0 9f 8f b3 ef b8 8f e2 80 8d f0 9f 8c 88`|4 code points: `1f3f3 fe0f 200d 1f308`|1 cluster: `[1f3f3 fe0f 200d 1f308]`|
|🇩🇪|8 bytes: `f0 9f 87 a9 f0 9f 87 aa`|2 code points: `1f1e9 1f1ea`|1 cluster: `[1f1e9 1f1ea]`|
This package provides a tool to iterate over these grapheme clusters. This may be used to determine the number of user-perceived characters, to split strings in their intended places, or to extract individual characters which form a unit.
## Installation
```bash
go get github.com/rivo/uniseg
```
## Basic Example
```go
package uniseg
import (
"fmt"
"github.com/rivo/uniseg"
)
func main() {
gr := uniseg.NewGraphemes("👍🏼!")
for gr.Next() {
fmt.Printf("%x ", gr.Runes())
}
// Output: [1f44d 1f3fc] [21]
}
```
## Documentation
Refer to https://godoc.org/github.com/rivo/uniseg for the package's documentation.
## Dependencies
This package does not depend on any packages outside the standard library.
## Your Feedback
Add your issue here on GitHub. Feel free to get in touch if you have any questions.
## Version
Version tags will be introduced once Golang modules are official. Consider this version 0.1.

8
vendor/github.com/rivo/uniseg/doc.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
/*
Package uniseg implements Unicode Text Segmentation according to Unicode
Standard Annex #29 (http://unicode.org/reports/tr29/).
At this point, only the determination of grapheme cluster boundaries is
implemented.
*/
package uniseg

3
vendor/github.com/rivo/uniseg/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/rivo/uniseg
go 1.12

268
vendor/github.com/rivo/uniseg/grapheme.go generated vendored Normal file
View File

@@ -0,0 +1,268 @@
package uniseg
import "unicode/utf8"
// The states of the grapheme cluster parser.
const (
grAny = iota
grCR
grControlLF
grL
grLVV
grLVTT
grPrepend
grExtendedPictographic
grExtendedPictographicZWJ
grRIOdd
grRIEven
)
// The grapheme cluster parser's breaking instructions.
const (
grNoBoundary = iota
grBoundary
)
// The grapheme cluster parser's state transitions. Maps (state, property) to
// (new state, breaking instruction, rule number). The breaking instruction
// always refers to the boundary between the last and next code point.
//
// This map is queried as follows:
//
// 1. Find specific state + specific property. Stop if found.
// 2. Find specific state + any property.
// 3. Find any state + specific property.
// 4. If only (2) or (3) (but not both) was found, stop.
// 5. If both (2) and (3) were found, use state and breaking instruction from
// the transition with the lower rule number, prefer (3) if rule numbers
// are equal. Stop.
// 6. Assume grAny and grBoundary.
var grTransitions = map[[2]int][3]int{
// GB5
{grAny, prCR}: {grCR, grBoundary, 50},
{grAny, prLF}: {grControlLF, grBoundary, 50},
{grAny, prControl}: {grControlLF, grBoundary, 50},
// GB4
{grCR, prAny}: {grAny, grBoundary, 40},
{grControlLF, prAny}: {grAny, grBoundary, 40},
// GB3.
{grCR, prLF}: {grAny, grNoBoundary, 30},
// GB6.
{grAny, prL}: {grL, grBoundary, 9990},
{grL, prL}: {grL, grNoBoundary, 60},
{grL, prV}: {grLVV, grNoBoundary, 60},
{grL, prLV}: {grLVV, grNoBoundary, 60},
{grL, prLVT}: {grLVTT, grNoBoundary, 60},
// GB7.
{grAny, prLV}: {grLVV, grBoundary, 9990},
{grAny, prV}: {grLVV, grBoundary, 9990},
{grLVV, prV}: {grLVV, grNoBoundary, 70},
{grLVV, prT}: {grLVTT, grNoBoundary, 70},
// GB8.
{grAny, prLVT}: {grLVTT, grBoundary, 9990},
{grAny, prT}: {grLVTT, grBoundary, 9990},
{grLVTT, prT}: {grLVTT, grNoBoundary, 80},
// GB9.
{grAny, prExtend}: {grAny, grNoBoundary, 90},
{grAny, prZWJ}: {grAny, grNoBoundary, 90},
// GB9a.
{grAny, prSpacingMark}: {grAny, grNoBoundary, 91},
// GB9b.
{grAny, prPreprend}: {grPrepend, grBoundary, 9990},
{grPrepend, prAny}: {grAny, grNoBoundary, 92},
// GB11.
{grAny, prExtendedPictographic}: {grExtendedPictographic, grBoundary, 9990},
{grExtendedPictographic, prExtend}: {grExtendedPictographic, grNoBoundary, 110},
{grExtendedPictographic, prZWJ}: {grExtendedPictographicZWJ, grNoBoundary, 110},
{grExtendedPictographicZWJ, prExtendedPictographic}: {grExtendedPictographic, grNoBoundary, 110},
// GB12 / GB13.
{grAny, prRegionalIndicator}: {grRIOdd, grBoundary, 9990},
{grRIOdd, prRegionalIndicator}: {grRIEven, grNoBoundary, 120},
{grRIEven, prRegionalIndicator}: {grRIOdd, grBoundary, 120},
}
// Graphemes implements an iterator over Unicode extended grapheme clusters,
// specified in the Unicode Standard Annex #29. Grapheme clusters correspond to
// "user-perceived characters". These characters often consist of multiple
// code points (e.g. the "woman kissing woman" emoji consists of 8 code points:
// woman + ZWJ + heavy black heart (2 code points) + ZWJ + kiss mark + ZWJ +
// woman) and the rules described in Annex #29 must be applied to group those
// code points into clusters perceived by the user as one character.
type Graphemes struct {
// The code points over which this class iterates.
codePoints []rune
// The (byte-based) indices of the code points into the original string plus
// len(original string). Thus, len(indices) = len(codePoints) + 1.
indices []int
// The current grapheme cluster to be returned. These are indices into
// codePoints/indices. If start == end, we either haven't started iterating
// yet (0) or the iteration has already completed (1).
start, end int
// The index of the next code point to be parsed.
pos int
// The current state of the code point parser.
state int
}
// NewGraphemes returns a new grapheme cluster iterator.
func NewGraphemes(s string) *Graphemes {
l := utf8.RuneCountInString(s)
codePoints := make([]rune, l)
indices := make([]int, l+1)
i := 0
for pos, r := range s {
codePoints[i] = r
indices[i] = pos
i++
}
indices[l] = len(s)
g := &Graphemes{
codePoints: codePoints,
indices: indices,
}
g.Next() // Parse ahead.
return g
}
// Next advances the iterator by one grapheme cluster and returns false if no
// clusters are left. This function must be called before the first cluster is
// accessed.
func (g *Graphemes) Next() bool {
g.start = g.end
// The state transition gives us a boundary instruction BEFORE the next code
// point so we always need to stay ahead by one code point.
// Parse the next code point.
for g.pos <= len(g.codePoints) {
// GB2.
if g.pos == len(g.codePoints) {
g.end = g.pos
g.pos++
break
}
// Determine the property of the next character.
nextProperty := property(g.codePoints[g.pos])
g.pos++
// Find the applicable transition.
var boundary bool
transition, ok := grTransitions[[2]int{g.state, nextProperty}]
if ok {
// We have a specific transition. We'll use it.
g.state = transition[0]
boundary = transition[1] == grBoundary
} else {
// No specific transition found. Try the less specific ones.
transAnyProp, okAnyProp := grTransitions[[2]int{g.state, prAny}]
transAnyState, okAnyState := grTransitions[[2]int{grAny, nextProperty}]
if okAnyProp && okAnyState {
// Both apply. We'll use a mix (see comments for grTransitions).
g.state = transAnyState[0]
boundary = transAnyState[1] == grBoundary
if transAnyProp[2] < transAnyState[2] {
g.state = transAnyProp[0]
boundary = transAnyProp[1] == grBoundary
}
} else if okAnyProp {
// We only have a specific state.
g.state = transAnyProp[0]
boundary = transAnyProp[1] == grBoundary
// This branch will probably never be reached because okAnyState will
// always be true given the current transition map. But we keep it here
// for future modifications to the transition map where this may not be
// true anymore.
} else if okAnyState {
// We only have a specific property.
g.state = transAnyState[0]
boundary = transAnyState[1] == grBoundary
} else {
// No known transition. GB999: Any x Any.
g.state = grAny
boundary = true
}
}
// If we found a cluster boundary, let's stop here. The current cluster will
// be the one that just ended.
if g.pos-1 == 0 /* GB1 */ || boundary {
g.end = g.pos - 1
break
}
}
return g.start != g.end
}
// Runes returns a slice of runes (code points) which corresponds to the current
// grapheme cluster. If the iterator is already past the end or Next() has not
// yet been called, nil is returned.
func (g *Graphemes) Runes() []rune {
if g.start == g.end {
return nil
}
return g.codePoints[g.start:g.end]
}
// Str returns a substring of the original string which corresponds to the
// current grapheme cluster. If the iterator is already past the end or Next()
// has not yet been called, an empty string is returned.
func (g *Graphemes) Str() string {
if g.start == g.end {
return ""
}
return string(g.codePoints[g.start:g.end])
}
// Bytes returns a byte slice which corresponds to the current grapheme cluster.
// If the iterator is already past the end or Next() has not yet been called,
// nil is returned.
func (g *Graphemes) Bytes() []byte {
if g.start == g.end {
return nil
}
return []byte(string(g.codePoints[g.start:g.end]))
}
// Positions returns the interval of the current grapheme cluster as byte
// positions into the original string. The first returned value "from" indexes
// the first byte and the second returned value "to" indexes the first byte that
// is not included anymore, i.e. str[from:to] is the current grapheme cluster of
// the original string "str". If Next() has not yet been called, both values are
// 0. If the iterator is already past the end, both values are 1.
func (g *Graphemes) Positions() (int, int) {
return g.indices[g.start], g.indices[g.end]
}
// Reset puts the iterator into its initial state such that the next call to
// Next() sets it to the first grapheme cluster again.
func (g *Graphemes) Reset() {
g.start, g.end, g.pos, g.state = 0, 0, 0, grAny
g.Next() // Parse ahead again.
}
// GraphemeClusterCount returns the number of user-perceived characters
// (grapheme clusters) for the given string. To calculate this number, it
// iterates through the string using the Graphemes iterator.
func GraphemeClusterCount(s string) (n int) {
g := NewGraphemes(s)
for g.Next() {
n++
}
return
}

1658
vendor/github.com/rivo/uniseg/properties.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
*.test

View File

@@ -1,64 +0,0 @@
## 0.1.2 (2015-03-28)
Add new functions
### Added
- `GithubUser()` extracts `github.user` ([**dstokes**](https://github.com/dstokes)), [#5](https://github.com/tcnksm/go-gitconfig/pull/5/commits)
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Nothing
## 0.1.1 (2014-10-28)
Add new functions
### Added
- `GithubToken()` extracts `github.token` ([**@sona-tar**](https://github.com/sona-tar), [#3](https://github.com/tcnksm/go-gitconfig/pull/3))
- `Entire()` try to extract value from entire git config. It's able to extract values from included config ([**@sona-tar**](https://github.com/sona-tar), [#3](https://github.com/tcnksm/go-gitconfig/pull/3))
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Nothing
## 0.1.0 (2014-09-08)
Initial release
### Added
- Fundamental features
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Nothing

View File

@@ -1,22 +0,0 @@
Copyright (c) 2014 tcnksm
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,84 +0,0 @@
go-gitconfig
====
[![GitHub release](http://img.shields.io/github/release/tcnksm/go-gitconfig.svg?style=flat-square)][release]
[![Wercker](http://img.shields.io/wercker/ci/544ee33aea87f6374f001483.svg?style=flat-square)][wercker]
[![Coveralls](http://img.shields.io/coveralls/tcnksm/go-gitconfig.svg?style=flat-square)][coveralls]
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)][license]
[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
[release]: https://github.com/tcnksm/go-gitconfig/releases
[wercker]: https://app.wercker.com/project/bykey/89c5a6e50a0daceec971ff5ce210164a
[coveralls]: https://coveralls.io/r/tcnksm/go-gitconfig
[license]: https://github.com/tcnksm/go-gitconfig/blob/master/LICENSE
[godocs]: http://godoc.org/github.com/tcnksm/go-gitconfig
`go-gitconfig` is a pacakge to use `gitconfig` values in Golang.
Sometimes you want to extract username or its email address **implicitly** in your tool.
Now most of developer use `git`, so we can use its configuration variables. `go-gitconfig` is for that.
`go-gitconfig` is very small, so it may not be included what you want to use.
If you want to use more git specific variable, check [Other](##VS).
## Usage
If you want to use git user name defined in `~/.gitconfig`:
```go
username, err := gitconfig.Username()
```
Or git user email defined in `~/.gitconfig`:
```go
email, err := gitconfig.Email()
```
Or, if you want to extract origin url of current project (from `.git/config`):
```go
url, err := gitconfig.OriginURL()
```
You can also extract value by key:
```go
editor, err := gitconfig.Global("core.editor")
```
```go
remote, err := gitconfig.Local("branch.master.remote")
```
See more details in document at [https://godoc.org/github.com/tcnksm/go-gitconfig](https://godoc.org/github.com/tcnksm/go-gitconfig).
## Install
To install, use `go get`:
```bash
$ go get -d github.com/tcnksm/go-gitconfig
```
## VS.
- [speedata/gogit](https://github.com/speedata/gogit)
- [libgit2/git2go](https://github.com/libgit2/git2go)
These packages have many features to use git from golang. `go-gitconfig` is very simple alternative and focus to extract information from gitconfig. `go-gitconfig` is used in [tcnksm/ghr](https://github.com/tcnksm/ghr).
## Contribution
1. Fork ([https://github.com/tcnksm/go-gitconfig/fork](https://github.com/tcnksm/go-gitconfig/fork))
1. Create a feature branch
1. Commit your changes
1. Rebase your local changes against the master branch
1. Run test suite with the `go test ./...` command and confirm that it passes
1. Run `gofmt -s`
1. Create new Pull Request
## Author
[tcnksm](https://github.com/tcnksm)

View File

@@ -1,113 +0,0 @@
// Package gitconfig enables you to use `~/.gitconfig` values in Golang.
//
// For a full guide visit http://github.com/tcnksm/go-gitconfig
//
// package main
//
// import (
// "github.com/tcnksm/go-gitconfig"
// "fmt"
// )
//
// func main() {
// user, err := gitconfig.Global("user.name")
// if err == nil {
// fmt.Println(user)
// }
// }
//
package gitconfig
import (
"bytes"
"fmt"
"io/ioutil"
"os/exec"
"regexp"
"strings"
"syscall"
)
// Entire extracts configuration value from `$HOME/.gitconfig` file ,
// `$GIT_CONFIG`, /etc/gitconfig or include.path files.
func Entire(key string) (string, error) {
return execGitConfig(key)
}
// Global extracts configuration value from `$HOME/.gitconfig` file or `$GIT_CONFIG`.
func Global(key string) (string, error) {
return execGitConfig("--global", key)
}
// Local extracts configuration value from current project repository.
func Local(key string) (string, error) {
return execGitConfig("--local", key)
}
// GithubUser extracts github.user name from `Entire gitconfig`
// This is same as Entire("github.user")
func GithubUser() (string, error) {
return Entire("github.user")
}
// Username extracts git user name from `Entire gitconfig`.
// This is same as Entire("user.name")
func Username() (string, error) {
return Entire("user.name")
}
// Email extracts git user email from `$HOME/.gitconfig` file or `$GIT_CONFIG`.
// This is same as Global("user.email")
func Email() (string, error) {
return Entire("user.email")
}
// OriginURL extract remote origin url from current project repository.
// This is same as Local("remote.origin.url")
func OriginURL() (string, error) {
return Local("remote.origin.url")
}
// Repository extract repository name of current project repository.
func Repository() (string, error) {
url, err := OriginURL()
if err != nil {
return "", err
}
repo := retrieveRepoName(url)
return repo, nil
}
// Github extracts github token from `Entire gitconfig`.
// This is same as Entire("github.token")
func GithubToken() (string, error) {
return Entire("github.token")
}
func execGitConfig(args ...string) (string, error) {
gitArgs := append([]string{"config", "--get", "--null"}, args...)
var stdout bytes.Buffer
cmd := exec.Command("git", gitArgs...)
cmd.Stdout = &stdout
cmd.Stderr = ioutil.Discard
err := cmd.Run()
if exitError, ok := err.(*exec.ExitError); ok {
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
if waitStatus.ExitStatus() == 1 {
return "", fmt.Errorf("the key `%s` is not found", args[len(args)-1])
}
}
return "", err
}
return strings.TrimRight(stdout.String(), "\000"), nil
}
var RepoNameRegexp = regexp.MustCompile(`.+/([^/]+)(\.git)?$`)
func retrieveRepoName(url string) string {
matched := RepoNameRegexp.FindStringSubmatch(url)
return strings.TrimSuffix(matched[1], ".git")
}

View File

@@ -1,20 +0,0 @@
box: tcnksm/gox
build:
steps:
- setup-go-workspace
- script:
name: install latest git
code: |
sudo add-apt-repository -y ppa:git-core/ppa
sudo apt-get -y update
sudo apt-get -y install git
- script:
name: git version
code: |
git version
- script:
name: go get
code: |
go get -t ./...
- tcnksm/goveralls:
token: $COVERALLS_REPO_TOKEN

15
vendor/modules.txt vendored
View File

@@ -7,6 +7,9 @@ github.com/atotto/clipboard
# github.com/aybabtme/humanlog v0.4.1
## explicit
github.com/aybabtme/humanlog
# github.com/cli/safeexec v1.0.0
## explicit
github.com/cli/safeexec
# github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
## explicit
github.com/cloudfoundry/jibber_jabber
@@ -104,7 +107,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
# github.com/jesseduffield/gocui v0.3.1-0.20201010224802-8a6768078fd7
# github.com/jesseduffield/gocui v0.3.1-0.20210208224444-2eecee85583d
## explicit
github.com/jesseduffield/gocui
# github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe
@@ -130,7 +133,7 @@ github.com/kr/logfmt
github.com/mattn/go-colorable
# github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-isatty
# github.com/mattn/go-runewidth v0.0.9
# github.com/mattn/go-runewidth v0.0.10
## explicit
github.com/mattn/go-runewidth
# github.com/mgutz/str v1.2.0
@@ -138,12 +141,17 @@ github.com/mattn/go-runewidth
github.com/mgutz/str
# github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-homedir
# github.com/nsf/termbox-go v0.0.0-20210114135735-d04385b850e8
## explicit
# github.com/onsi/ginkgo v1.10.3
## explicit
# github.com/onsi/gomega v1.7.1
## explicit
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
# github.com/rivo/uniseg v0.2.0
## explicit
github.com/rivo/uniseg
# github.com/sahilm/fuzzy v0.1.0
## explicit
github.com/sahilm/fuzzy
@@ -158,9 +166,6 @@ github.com/spkg/bom
# github.com/stretchr/testify v1.4.0
## explicit
github.com/stretchr/testify/assert
# github.com/tcnksm/go-gitconfig v0.1.2
## explicit
github.com/tcnksm/go-gitconfig
# github.com/xanzy/ssh-agent v0.2.1
github.com/xanzy/ssh-agent
# golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0