Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48ab294922 | ||
|
|
555996642d | ||
|
|
aad4269128 | ||
|
|
b3fbe8453f | ||
|
|
ffac0e6420 | ||
|
|
7371765d47 | ||
|
|
7f75808499 | ||
|
|
778cb14dc4 | ||
|
|
00c679a3f0 | ||
|
|
d9ec6f9890 | ||
|
|
9858bfe4c3 | ||
|
|
feada164d0 | ||
|
|
a1706ef2ee | ||
|
|
1bd3aa9708 | ||
|
|
d0aaf94068 | ||
|
|
8054d85782 | ||
|
|
31a8be5f32 | ||
|
|
ca04655a09 | ||
|
|
4281cc2884 | ||
|
|
8013f18177 | ||
|
|
c965333756 | ||
|
|
54408a912c | ||
|
|
3839719154 | ||
|
|
e8c4bf20f6 | ||
|
|
fbadbdd771 | ||
|
|
c6aee678c0 | ||
|
|
c4c27262f2 | ||
|
|
a253b0b897 | ||
|
|
5ca47c3f47 | ||
|
|
456c593c72 | ||
|
|
3b264806ef | ||
|
|
92e75d4602 | ||
|
|
7add598235 | ||
|
|
2853aba951 | ||
|
|
8cc444182f | ||
|
|
2a25ce014b | ||
|
|
411c02324f | ||
|
|
2f50cbf2b8 | ||
|
|
417cb97dc6 | ||
|
|
fb5d25c9e9 | ||
|
|
f580572e70 | ||
|
|
ba30e9e450 | ||
|
|
30d79bfa2c | ||
|
|
4d9d2b134f | ||
|
|
6cb0a14f33 | ||
|
|
51d3e679bf | ||
|
|
bab884f1df | ||
|
|
1730089b09 | ||
|
|
8c823965ab | ||
|
|
5a624764a8 | ||
|
|
b541987007 | ||
|
|
2d4801c39d | ||
|
|
7fe54e889d | ||
|
|
f6a9c727fa | ||
|
|
9067c3be3e |
35
.goreleaser.yml
Normal file
35
.goreleaser.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
# This is an example goreleaser.yaml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
builds:
|
||||
-
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
main: .
|
||||
goos:
|
||||
- freebsd
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
env_files:
|
||||
github_token: ./github_token
|
||||
40
.travis.yml
40
.travis.yml
@@ -1,40 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
env:
|
||||
- DEP_VERSION="0.5.0"
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.x
|
||||
- go: 1.7
|
||||
env: LATEST=true
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
before_install:
|
||||
# Download the binary to bin folder in $GOPATH
|
||||
- curl -L -s https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-386 -o $GOPATH/bin/dep
|
||||
# Make the binary executable
|
||||
- chmod +x $GOPATH/bin/dep
|
||||
- ls $GOPATH/bin/
|
||||
- dep ensure
|
||||
- go get github.com/mitchellh/gox
|
||||
install:
|
||||
-
|
||||
script:
|
||||
# - go get -v ./...
|
||||
# - diff -u <(echo -n) <(gofmt -d .) # can't make gofmt ignore vendor directory
|
||||
# - go vet $(go list ./... | grep -v /vendor/)
|
||||
- if [ "${LATEST}" = "true" ]; then gox -os="linux darwin windows" -arch="amd64" -output="lazygit.." -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...; fi
|
||||
deploy:
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
api_key:
|
||||
secure: TnB8I+swjicHuGTXk3ncm1Aaa12eIJqWV/Lhcnbb01i39p6+fyn3vDMdWPcejt3R8gcJqv4wyP8UQVO9G1qkLppt6V/qAuY5x6nX0MgEa3t+8JLJnGYHZYsuIgan/ecAmeu5+6dgUhr9Oq6zQOEv/O88NsALzMlqnEQNXI8XSoScfhkiVDIp3zWov0vBizCdThnNgTx9zRpJVoqxmhWvgt+me2+fOhSx1Y+3ZA2gE7zq8IFAbxp36d0rsR5lKqmTuF+YsF9iQ7Ar+xCjbRunLsZx+VwGqGfpS/qS7EwsEqBI0vEO76eFJkwEsIzOvJiFNhBDUu3upquBFMT4uzxRxH3eV+J4mZtu29UDLdvKI5Q730Lk9AgmH4now+RmP08M0SEXJa+AnHeuBv2u1iU5bu+sI6CORVQzKQwOph9AABDjSZ54wrXIpYEeIW2sz8nx+hiG6QL1mqfM/l+55BR69u3vxKYMryQBxPuzhZCTOqqI4uahlb6GIUNZJ9vGZeIA9HFJq3ymW8cdrpYzhKf3Nx9jK+Yb81h5/AHq9iChXEC63VPCDXXGRllh2UYWNYCaAdtk+ekpLR8299e4CaEregy6g5U2S3/xrBKl87miu1uJ/fquXoxGdSU+JcmsmXZ26sGIU2TCYdNjSfIgpOyfMmB4JNtKHqWRHA9Fe42CRpA=
|
||||
file:
|
||||
- lazygit.windows.amd64.exe
|
||||
- lazygit.darwin.amd64
|
||||
- lazygit.linux.amd64
|
||||
on:
|
||||
repo: jesseduffield/lazygit
|
||||
tags: true
|
||||
condition: $LATEST = true
|
||||
@@ -22,7 +22,7 @@ welcome your pull requests:
|
||||
## Code of conduct
|
||||
Please note by participating in this project, you agree to abide by the [code of conduct].
|
||||
|
||||
[code of conduct]: https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md
|
||||
[code of conduct]: https://github.com/jesseduffield/lazygit/blob/master/CODE-OF-CONDUCT.md
|
||||
|
||||
## Any contributions you make will be under the MIT Software License
|
||||
In short, when you submit code changes, your submissions are understood to be
|
||||
@@ -31,4 +31,4 @@ covers the project. Feel free to contact the maintainers if that's a concern.
|
||||
|
||||
## Report bugs using Github's [issues](https://github.com/jesseduffield/lazygit/issues)
|
||||
We use GitHub issues to track public bugs. Report a bug by [opening a new
|
||||
issue](https://github.com/jesseduffield/lazygit/issues/new); it's that easy!
|
||||
issue](https://github.com/jesseduffield/lazygit/issues/new); it's that easy!
|
||||
|
||||
39
Gopkg.lock
generated
39
Gopkg.lock
generated
@@ -2,10 +2,10 @@
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
|
||||
digest = "1:ade392a843b2035effb4b4a2efa2c3bab3eb29b992e98bacf9c898b0ecb54e45"
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
@@ -14,55 +14,63 @@
|
||||
digest = "1:4a8ed9b8cf22bd03bee5d74179fa06a282e4a73b6de949f7a865ff56cd2537e0"
|
||||
name = "github.com/golang-collections/collections"
|
||||
packages = ["stack"]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "604e922904d35e97f98a774db7881f049cd8d970"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f086cf183e423bf1926f4de05f47bf57132fe5db9c99e464f733ce925280fc81"
|
||||
digest = "1:82b3bbc50ba7b6065b0229ebf0fc990a76e47410acd510294e435aeb3523293e"
|
||||
name = "github.com/jesseduffield/gocui"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "3c923f53ac9952af649af04a067405843558d56f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
|
||||
digest = "1:08c231ec84231a7e23d67e4b58f975e1423695a32467a362ee55a803f9de8061"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
|
||||
digest = "1:bc4f7eec3b7be8c6cb1f0af6c1e3333d5bb71072951aaaae2f05067b0803f287"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e2d1d410fb367567c2b53ed9e2d719d3c1f0891397bb2fa49afd747cfbf1e8e4"
|
||||
digest = "1:cb591533458f6eb6e2c1065ff3eac6b50263d7847deb23fc9f79b25bc608970e"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c9b6e36dbd23f8403a04493376916ca5dad8c01b2da5ae0a05e6a468eb0b6f24"
|
||||
digest = "1:34d9354c2c5d916c05864327553047df59fc10e86ff1f408e4136eba0a25a5ec"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cd5ffc5bda4e0296ab3e4de90dbb415259c78e45e7fab13694b14cde8ab74541"
|
||||
name = "github.com/tcnksm/go-gitconfig"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d154598bacbf4501c095a309753c5d4af66caa81"
|
||||
version = "v0.1.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9ec6ad2659635ba0974dd7e55bf84233523eb4e7535c9a2fddaefc4cc62a3eac"
|
||||
digest = "1:4d8a79fbc6fa348fc94afa4235947c5196b7900ed71b94aa5fcbc7e273d150e1"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
pruneopts = "UT"
|
||||
pruneopts = "NUT"
|
||||
revision = "0ffbfd41fbef8ffcf9b62b0b0aa3a5873ed7a4fe"
|
||||
|
||||
[solve-meta]
|
||||
@@ -72,6 +80,7 @@
|
||||
"github.com/fatih/color",
|
||||
"github.com/golang-collections/collections/stack",
|
||||
"github.com/jesseduffield/gocui",
|
||||
"github.com/tcnksm/go-gitconfig",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
25
README.md
25
README.md
@@ -9,16 +9,20 @@ Are YOU tired of typing every git command directly into the terminal, but you're
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
### Via binary release
|
||||
You can download a binary release [here](https://github.com/jesseduffield/lazygit/releases)
|
||||
You may need to grant execute permission via `chmod +x ./<filename>`.
|
||||
You can move this file to your local /bin directory and rename to `lazygit` so that you can run it anywhere
|
||||
(Easier-install coming soon)
|
||||
|
||||
### Via Go
|
||||
In a terminal call this command:
|
||||
`go get github.com/jesseduffield/lazygit`
|
||||
(if you don't have Go installed, you can follow the installation guide [here](https://golang.org/doc/install).
|
||||
|
||||
Then just call `lazygit` in your terminal inside a git repository.
|
||||
|
||||
If you want, you can also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or whichever rc file you're using).
|
||||
|
||||
Please note:
|
||||
If you get an error claiming that lazygit cannot be found or is not defined, you may need to add `~/go/bin` to your $PATH (MacOS/Linux), or `%HOME%\go\bin` (Windows)
|
||||
If you get an error claiming that lazygit cannot be found or is not defined, you may need to add `~/go/bin` to your $PATH (MacOS/Linux), or `%HOME%\go\bin` (Windows). Not to be mistaked for `C:\Go\bin` (which is for Go's own binaries, not apps like Lazygit)
|
||||
|
||||
### Ubuntu
|
||||
Packages for Ubuntu 16.04 and up are available via Launchpad PPA.
|
||||
@@ -31,6 +35,13 @@ sudo apt-get update
|
||||
sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
Call `lazygit` in your terminal inside a git repository.
|
||||
If you want, you can also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or whichever rc file you're using).
|
||||
Basic tutorial [Here](https://www.youtube.com/watch?v=VDXvbHZYeKY)
|
||||
[Keybindings](https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md)
|
||||
|
||||
## Cool features
|
||||
- Adding files easily
|
||||
- Resolving merge conflicts
|
||||
@@ -45,14 +56,12 @@ sudo apt-get install lazygit
|
||||
### Viewing commit diffs
|
||||

|
||||
|
||||
## Docs
|
||||
[Keybindings](https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md)
|
||||
|
||||
## Milestones
|
||||
- [ ] Easy Installation (homebrew, release binaries)
|
||||
- [ ] Configurable Keybindings
|
||||
- [ ] Configurable Color Themes
|
||||
- [ ] Spawning Subprocesses (help needed - have a look at https://github.com/jesseduffield/lazygit/pull/18)
|
||||
- [ ] Maintainability
|
||||
- [ ] Performance
|
||||
- [ ] i18n
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
space: toggle staged
|
||||
c: commit changes
|
||||
shift+S: stash files
|
||||
o: open (osx only)
|
||||
t: add patched (i.e. pick chunks of a file to add)
|
||||
o: open
|
||||
e: edit
|
||||
s: open in sublime (requires 'subl' command)
|
||||
v: open in vscode (requires 'code' command)
|
||||
i: add to .gitignore
|
||||
|
||||
@@ -63,6 +63,21 @@ func handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
return handleFileSelect(g, v)
|
||||
}
|
||||
|
||||
func handleAddPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err == ErrNoFiles {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !file.HasUnstagedChanges {
|
||||
return createErrorPanel(g, "File has no unstaged changes to add")
|
||||
}
|
||||
gitAddPatch(g, file.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func getSelectedFile(g *gocui.Gui) (GitFile, error) {
|
||||
if len(state.GitFiles) == 0 {
|
||||
return GitFile{}, ErrNoFiles
|
||||
@@ -115,12 +130,12 @@ func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error {
|
||||
"S": "stash files",
|
||||
"c": "commit changes",
|
||||
"o": "open",
|
||||
"s": "sublime",
|
||||
"v": "vscode",
|
||||
"i": "ignore",
|
||||
"d": "delete",
|
||||
"space": "toggle staged",
|
||||
"R": "refresh",
|
||||
"t": "add patch",
|
||||
"e": "edit",
|
||||
}
|
||||
if state.HasMergeConflicts {
|
||||
optionsMap["a"] = "abort merge"
|
||||
@@ -163,7 +178,7 @@ func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
if message == "" {
|
||||
return createErrorPanel(g, "You cannot commit without a commit message")
|
||||
}
|
||||
if output, err := gitCommit(message); err != nil {
|
||||
if output, err := gitCommit(g, message); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
refreshFiles(g)
|
||||
@@ -172,7 +187,7 @@ func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (string, error)) error {
|
||||
func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(*gocui.Gui, string) (string, error)) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != ErrNoFiles {
|
||||
@@ -180,18 +195,24 @@ func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (string, err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if output, err := open(file.Name); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
if _, err := open(g, file.Name); err != nil {
|
||||
return createErrorPanel(g, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleFileEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, editFile)
|
||||
}
|
||||
|
||||
func handleFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, openFile)
|
||||
}
|
||||
|
||||
func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, sublimeOpenFile)
|
||||
}
|
||||
|
||||
func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, vsCodeOpenFile)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
gitconfig "github.com/tcnksm/go-gitconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoCheckedOutBranch : When we have no checked out branch
|
||||
ErrNoCheckedOutBranch = errors.New("No currently checked out branch")
|
||||
|
||||
// ErrNoOpenCommand : When we don't know which command to use to open a file
|
||||
ErrNoOpenCommand = errors.New("Unsure what command to use to open this file")
|
||||
)
|
||||
|
||||
// GitFile : A staged/unstaged file
|
||||
@@ -263,23 +268,73 @@ func runCommand(command string) (string, error) {
|
||||
commandStartTime := time.Now()
|
||||
commandLog(command)
|
||||
splitCmd := strings.Split(command, " ")
|
||||
devLog(splitCmd)
|
||||
cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput()
|
||||
devLog("run command time: ", time.Now().Sub(commandStartTime))
|
||||
return sanitisedCommandOutput(cmdOut, err)
|
||||
}
|
||||
|
||||
func openFile(filename string) (string, error) {
|
||||
return runCommand("open " + filename)
|
||||
}
|
||||
|
||||
func vsCodeOpenFile(filename string) (string, error) {
|
||||
func vsCodeOpenFile(g *gocui.Gui, filename string) (string, error) {
|
||||
return runCommand("code -r " + filename)
|
||||
}
|
||||
|
||||
func sublimeOpenFile(filename string) (string, error) {
|
||||
func sublimeOpenFile(g *gocui.Gui, filename string) (string, error) {
|
||||
return runCommand("subl " + filename)
|
||||
}
|
||||
|
||||
func openFile(g *gocui.Gui, filename string) (string, error) {
|
||||
cmdName, cmdTrail, err := getOpenCommand()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return runCommand(cmdName + " " + filename + cmdTrail)
|
||||
}
|
||||
|
||||
func getOpenCommand() (string, string, error) {
|
||||
//NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX)
|
||||
trailMap := map[string]string{
|
||||
"xdg-open": " &>/dev/null &",
|
||||
"cygstart": "",
|
||||
"open": "",
|
||||
}
|
||||
for name, trail := range trailMap {
|
||||
if out, _ := runCommand("which " + name); out != "exit status 1" {
|
||||
return name, trail, nil
|
||||
}
|
||||
}
|
||||
return "", "", ErrNoOpenCommand
|
||||
}
|
||||
|
||||
func gitAddPatch(g *gocui.Gui, filename string) {
|
||||
runSubProcess(g, "git", "add", "-p", filename)
|
||||
}
|
||||
|
||||
func editFile(g *gocui.Gui, filename string) (string, error) {
|
||||
editor, _ := gitconfig.Global("core.editor")
|
||||
if editor == "" {
|
||||
editor = os.Getenv("VISUAL")
|
||||
}
|
||||
if editor == "" {
|
||||
editor = os.Getenv("EDITOR")
|
||||
}
|
||||
if editor == "" {
|
||||
return "", createErrorPanel(g, "No editor defined in $VISUAL, $EDITOR, or git config.")
|
||||
}
|
||||
runSubProcess(g, editor, filename)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func runSubProcess(g *gocui.Gui, cmdName string, commandArgs ...string) {
|
||||
subprocess = exec.Command(cmdName, commandArgs...)
|
||||
subprocess.Stdin = os.Stdin
|
||||
subprocess.Stdout = os.Stdout
|
||||
subprocess.Stderr = os.Stderr
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
return ErrSubprocess
|
||||
})
|
||||
}
|
||||
|
||||
func getBranchGraph(branch string, baseBranch string) (string, error) {
|
||||
return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch)
|
||||
|
||||
@@ -403,7 +458,12 @@ func removeFile(file GitFile) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func gitCommit(message string) (string, error) {
|
||||
func gitCommit(g *gocui.Gui, message string) (string, error) {
|
||||
gpgsign, _ := gitconfig.Global("commit.gpgsign")
|
||||
if gpgsign != "" {
|
||||
runSubProcess(g, "bash", "-c", "git commit -m \""+message+"\"")
|
||||
return "", nil
|
||||
}
|
||||
return runDirectCommand("git commit -m \"" + message + "\"")
|
||||
}
|
||||
|
||||
|
||||
1
github_token
Normal file
1
github_token
Normal file
@@ -0,0 +1 @@
|
||||
TnB8I+swjicHuGTXk3ncm1Aaa12eIJqWV/Lhcnbb01i39p6+fyn3vDMdWPcejt3R8gcJqv4wyP8UQVO9G1qkLppt6V/qAuY5x6nX0MgEa3t+8JLJnGYHZYsuIgan/ecAmeu5+6dgUhr9Oq6zQOEv/O88NsALzMlqnEQNXI8XSoScfhkiVDIp3zWov0vBizCdThnNgTx9zRpJVoqxmhWvgt+me2+fOhSx1Y+3ZA2gE7zq8IFAbxp36d0rsR5lKqmTuF+YsF9iQ7Ar+xCjbRunLsZx+VwGqGfpS/qS7EwsEqBI0vEO76eFJkwEsIzOvJiFNhBDUu3upquBFMT4uzxRxH3eV+J4mZtu29UDLdvKI5Q730Lk9AgmH4now+RmP08M0SEXJa+AnHeuBv2u1iU5bu+sI6CORVQzKQwOph9AABDjSZ54wrXIpYEeIW2sz8nx+hiG6QL1mqfM/l+55BR69u3vxKYMryQBxPuzhZCTOqqI4uahlb6GIUNZJ9vGZeIA9HFJq3ymW8cdrpYzhKf3Nx9jK+Yb81h5/AHq9iChXEC63VPCDXXGRllh2UYWNYCaAdtk+ekpLR8299e4CaEregy6g5U2S3/xrBKl87miu1uJ/fquXoxGdSU+JcmsmXZ26sGIU2TCYdNjSfIgpOyfMmB4JNtKHqWRHA9Fe42CRpA=
|
||||
94
gui.go
94
gui.go
@@ -5,8 +5,6 @@ import (
|
||||
// "io"
|
||||
// "io/ioutil"
|
||||
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -71,75 +69,9 @@ func handleRefresh(g *gocui.Gui, v *gocui.View) error {
|
||||
return refreshSidePanels(g)
|
||||
}
|
||||
|
||||
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
|
||||
// is only handled if the given view has focus, or handled globally if the view
|
||||
// is ""
|
||||
type Binding struct {
|
||||
ViewName string
|
||||
Handler func(*gocui.Gui, *gocui.View) error
|
||||
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
||||
Modifier gocui.Modifier
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
bindings := []Binding{
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: "", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain},
|
||||
Binding{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles},
|
||||
Binding{ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles},
|
||||
Binding{ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh},
|
||||
Binding{ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress},
|
||||
Binding{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
|
||||
Binding{ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
|
||||
Binding{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
|
||||
Binding{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
|
||||
Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile},
|
||||
Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
|
||||
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
|
||||
Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
|
||||
Binding{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
|
||||
Binding{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk},
|
||||
Binding{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
|
||||
Binding{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot},
|
||||
Binding{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress},
|
||||
Binding{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName},
|
||||
Binding{ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout},
|
||||
Binding{ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch},
|
||||
Binding{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge},
|
||||
Binding{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown},
|
||||
Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
|
||||
Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
|
||||
Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
|
||||
Binding{ViewName: "stash", Key: 'k', Modifier: gocui.ModNone, Handler: handleStashPop},
|
||||
Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
g.Highlight = true
|
||||
g.SelFgColor = gocui.ColorWhite | gocui.AttrBold
|
||||
if runtime.GOOS != "windows" {
|
||||
g.FgColor = gocui.ColorBlack
|
||||
}
|
||||
width, height := g.Size()
|
||||
leftSideWidth := width / 3
|
||||
statusFilesBoundary := 2
|
||||
@@ -225,14 +157,23 @@ func layout(g *gocui.Gui) error {
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", -1, optionsTop, width, optionsTop+2, 0); err != nil {
|
||||
if v, err := g.SetView("options", -1, optionsTop, width-len(version)-2, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorBlue
|
||||
v.Frame = false
|
||||
v.Title = "Options"
|
||||
}
|
||||
|
||||
if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorGreen
|
||||
v.Frame = false
|
||||
renderString(g, "version", version)
|
||||
|
||||
// these are only called once
|
||||
handleFileSelect(g, filesView)
|
||||
@@ -261,10 +202,10 @@ func updateLoader(g *gocui.Gui) {
|
||||
}
|
||||
}
|
||||
|
||||
func run() {
|
||||
func run() (err error) {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
return
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
@@ -283,13 +224,12 @@ func run() {
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
if err = keybindings(g); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
err = g.MainLoop()
|
||||
return
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
68
keybindings.go
Normal file
68
keybindings.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
|
||||
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
|
||||
// is only handled if the given view has focus, or handled globally if the view
|
||||
// is ""
|
||||
type Binding struct {
|
||||
ViewName string
|
||||
Handler func(*gocui.Gui, *gocui.View) error
|
||||
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
||||
Modifier gocui.Modifier
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
bindings := []Binding{
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: "", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain},
|
||||
Binding{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles},
|
||||
Binding{ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles},
|
||||
Binding{ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh},
|
||||
Binding{ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress},
|
||||
Binding{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
|
||||
Binding{ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
|
||||
Binding{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
|
||||
Binding{ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: handleFileEdit},
|
||||
Binding{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
|
||||
Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile},
|
||||
Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
|
||||
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
|
||||
Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
|
||||
Binding{ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: handleAddPatch},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
|
||||
Binding{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
|
||||
Binding{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk},
|
||||
Binding{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
|
||||
Binding{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot},
|
||||
Binding{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress},
|
||||
Binding{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName},
|
||||
Binding{ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout},
|
||||
Binding{ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch},
|
||||
Binding{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge},
|
||||
Binding{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown},
|
||||
Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
|
||||
Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
|
||||
Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
|
||||
Binding{ViewName: "stash", Key: 'k', Modifier: gocui.ModNone, Handler: handleStashPop},
|
||||
Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
main.go
40
main.go
@@ -1,19 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// ErrSubProcess is raised when we are running a subprocess
|
||||
var (
|
||||
startTime time.Time
|
||||
debugging bool
|
||||
ErrSubprocess = errors.New("running subprocess")
|
||||
subprocess *exec.Cmd
|
||||
startTime time.Time
|
||||
|
||||
commit string
|
||||
version = "unversioned"
|
||||
|
||||
date string
|
||||
debuggingFlag = flag.Bool("debug", false, "a boolean")
|
||||
versionFlag = flag.Bool("v", false, "Print the current version")
|
||||
)
|
||||
|
||||
func homeDirectory() string {
|
||||
@@ -37,7 +49,7 @@ func commandLog(objects ...interface{}) {
|
||||
}
|
||||
|
||||
func localLog(colour color.Attribute, path string, objects ...interface{}) {
|
||||
if !debugging {
|
||||
if !*debuggingFlag {
|
||||
return
|
||||
}
|
||||
f, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
@@ -58,12 +70,24 @@ func navigateToRepoRootDirectory() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
debuggingPointer := flag.Bool("debug", false, "a boolean")
|
||||
flag.Parse()
|
||||
debugging = *debuggingPointer
|
||||
devLog("\n\n\n\n\n\n\n\n\n\n")
|
||||
startTime = time.Now()
|
||||
devLog("\n\n\n\n\n\n\n\n\n\n")
|
||||
flag.Parse()
|
||||
if *versionFlag {
|
||||
fmt.Printf("commit=%s, build date=%s, version=%s", commit, date, version)
|
||||
os.Exit(0)
|
||||
}
|
||||
verifyInGitRepo()
|
||||
navigateToRepoRootDirectory()
|
||||
run()
|
||||
for {
|
||||
if err := run(); err != nil {
|
||||
if err == gocui.ErrQuit {
|
||||
break
|
||||
} else if err == ErrSubprocess {
|
||||
subprocess.Run()
|
||||
} else {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
test/shell_script_input_prompt.sh
Executable file
7
test/shell_script_input_prompt.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# For testing subprocesses that require input
|
||||
# Ask the user for login details
|
||||
read -p 'Username: ' user
|
||||
read -sp 'Password: ' pass
|
||||
echo
|
||||
echo Hello $user
|
||||
22
vendor/github.com/tcnksm/go-gitconfig/LICENSE
generated
vendored
Normal file
22
vendor/github.com/tcnksm/go-gitconfig/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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.
|
||||
113
vendor/github.com/tcnksm/go-gitconfig/gitconfig.go
generated
vendored
Normal file
113
vendor/github.com/tcnksm/go-gitconfig/gitconfig.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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")
|
||||
}
|
||||
Reference in New Issue
Block a user