Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058bcddc53 | ||
|
|
8288de0c84 | ||
|
|
1da2afd450 | ||
|
|
03de51747e | ||
|
|
3d698cd7c1 | ||
|
|
a48cc245e7 | ||
|
|
9ed3a8ee05 | ||
|
|
64daf1310d | ||
|
|
e5ba0d9d9c | ||
|
|
50e4e9d58d | ||
|
|
03b9db5e0a | ||
|
|
043cb2ea44 | ||
|
|
a62d70fbd5 | ||
|
|
053e80a08e | ||
|
|
b726dcc770 | ||
|
|
9df133ed8c | ||
|
|
50dd7b00c3 | ||
|
|
ccbd2c924b | ||
|
|
52d5c3beeb | ||
|
|
c43416891e | ||
|
|
56a573de86 | ||
|
|
e7fff2529c | ||
|
|
78867647d1 | ||
|
|
09f32d4f84 | ||
|
|
6f0f70bd92 | ||
|
|
6df15ddf6e | ||
|
|
922c0887f1 | ||
|
|
d7c9243880 | ||
|
|
f42a595aba | ||
|
|
797722ec12 | ||
|
|
bb4bf23c5c | ||
|
|
f3aacbd253 | ||
|
|
106fce26b5 |
16
.github/workflows/cd.yml
vendored
16
.github/workflows/cd.yml
vendored
@@ -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@@')"
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -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:
|
||||
|
||||
@@ -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] "
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
8
go.mod
@@ -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
16
go.sum
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
56
pkg/commands/gitconfig.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}}",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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: `\"`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
11
pkg/secureexec/secureexec_default.go
Normal file
11
pkg/secureexec/secureexec_default.go
Normal 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...)
|
||||
}
|
||||
30
pkg/secureexec/secureexec_windows.go
Normal file
30
pkg/secureexec/secureexec_windows.go
Normal 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...)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
25
vendor/github.com/cli/safeexec/LICENSE
generated
vendored
Normal 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
40
vendor/github.com/cli/safeexec/README.md
generated
vendored
Normal 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
3
vendor/github.com/cli/safeexec/go.mod
generated
vendored
Normal 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
9
vendor/github.com/cli/safeexec/lookpath.go
generated
vendored
Normal 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
120
vendor/github.com/cli/safeexec/lookpath_windows.go
generated
vendored
Normal 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}
|
||||
}
|
||||
19
vendor/github.com/jesseduffield/gocui/edit.go
generated
vendored
19
vendor/github.com/jesseduffield/gocui/edit.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/mattn/go-runewidth/go.mod
generated
vendored
2
vendor/github.com/mattn/go-runewidth/go.mod
generated
vendored
@@ -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
2
vendor/github.com/mattn/go-runewidth/go.sum
generated
vendored
Normal 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=
|
||||
80
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
80
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
@@ -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
21
vendor/github.com/rivo/uniseg/LICENSE.txt
generated
vendored
Normal 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
62
vendor/github.com/rivo/uniseg/README.md
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# Unicode Text Segmentation for Go
|
||||
|
||||
[](https://godoc.org/github.com/rivo/uniseg)
|
||||
[](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
8
vendor/github.com/rivo/uniseg/doc.go
generated
vendored
Normal 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
3
vendor/github.com/rivo/uniseg/go.mod
generated
vendored
Normal 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
268
vendor/github.com/rivo/uniseg/grapheme.go
generated
vendored
Normal 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
1658
vendor/github.com/rivo/uniseg/properties.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
vendor/github.com/tcnksm/go-gitconfig/.gitignore
generated
vendored
1
vendor/github.com/tcnksm/go-gitconfig/.gitignore
generated
vendored
@@ -1 +0,0 @@
|
||||
*.test
|
||||
64
vendor/github.com/tcnksm/go-gitconfig/CHANGELOG.md
generated
vendored
64
vendor/github.com/tcnksm/go-gitconfig/CHANGELOG.md
generated
vendored
@@ -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
|
||||
|
||||
|
||||
22
vendor/github.com/tcnksm/go-gitconfig/LICENSE
generated
vendored
22
vendor/github.com/tcnksm/go-gitconfig/LICENSE
generated
vendored
@@ -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.
|
||||
84
vendor/github.com/tcnksm/go-gitconfig/README.md
generated
vendored
84
vendor/github.com/tcnksm/go-gitconfig/README.md
generated
vendored
@@ -1,84 +0,0 @@
|
||||
go-gitconfig
|
||||
====
|
||||
|
||||
[][release]
|
||||
[][wercker]
|
||||
[][coveralls]
|
||||
[][license]
|
||||
[][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)
|
||||
113
vendor/github.com/tcnksm/go-gitconfig/gitconfig.go
generated
vendored
113
vendor/github.com/tcnksm/go-gitconfig/gitconfig.go
generated
vendored
@@ -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")
|
||||
}
|
||||
20
vendor/github.com/tcnksm/go-gitconfig/wercker.yml
generated
vendored
20
vendor/github.com/tcnksm/go-gitconfig/wercker.yml
generated
vendored
@@ -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
15
vendor/modules.txt
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user