Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a00bbf709f | ||
|
|
b90e66dca1 | ||
|
|
77eea90b0b | ||
|
|
79b012990b | ||
|
|
45c0b9404a | ||
|
|
50924f31b0 | ||
|
|
705d273ad3 | ||
|
|
30c6fc2300 | ||
|
|
16b202f227 | ||
|
|
7c1dbb7c66 | ||
|
|
41152881db | ||
|
|
d33fff54a3 | ||
|
|
3bf621de65 | ||
|
|
0c84055abd | ||
|
|
5d9df3ca07 | ||
|
|
6581bec1ce | ||
|
|
ce3f81a2d3 | ||
|
|
bc479a9bf5 | ||
|
|
83a9a6d1f5 | ||
|
|
ca0b3debe9 | ||
|
|
84a2b1ceb9 | ||
|
|
5951811a8b | ||
|
|
968f3b2c1c | ||
|
|
aacbb72e6c | ||
|
|
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 | ||
|
|
aa6a0bd43b | ||
|
|
d781a8e650 | ||
|
|
4528cf95b9 | ||
|
|
dd6cdc02f0 | ||
|
|
2311675d08 | ||
|
|
cda365a93f | ||
|
|
b567a86ee5 | ||
|
|
a6cb0feff1 | ||
|
|
af766c848c | ||
|
|
ade0dc447e | ||
|
|
8aca30ee4c | ||
|
|
d5a73c0166 | ||
|
|
53044e20c9 | ||
|
|
59ea1491db | ||
|
|
69f47fddc4 | ||
|
|
75e1a2d62f | ||
|
|
b715236c0a | ||
|
|
6925ceeb6e | ||
|
|
5a624764a8 | ||
|
|
b541987007 | ||
|
|
2d4801c39d | ||
|
|
7fe54e889d | ||
|
|
f6a9c727fa | ||
|
|
9067c3be3e |
59
.goreleaser.yml
Normal file
59
.goreleaser.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
# 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
|
||||
goos:
|
||||
- freebsd
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
brew:
|
||||
# Reporitory to push the tap to.
|
||||
github:
|
||||
owner: jesseduffield
|
||||
name: homebrew-lazygit
|
||||
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: "https://github.com/jesseduffield/lazygit/"
|
||||
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: "A simple terminal UI for git commands, written in Go"
|
||||
|
||||
# # Packages your package depends on.
|
||||
# dependencies:
|
||||
# - git
|
||||
# - zsh
|
||||
|
||||
# # Packages that conflict with your package.
|
||||
# conflicts:
|
||||
# - svn
|
||||
# - bash
|
||||
|
||||
# test comment to see if goreleaser only releases on new commits
|
||||
38
.travis.yml
38
.travis.yml
@@ -1,38 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
env:
|
||||
- DEP_VERSION="0.5.0"
|
||||
matrix:
|
||||
include:
|
||||
- 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
|
||||
- dep ensure
|
||||
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
|
||||
74
CODE-OF-CONDUCT.md
Normal file
74
CODE-OF-CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the [project leader](https://github.com/jesseduffield).
|
||||
All complaints will be reviewed and investigated and will result in a response that
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
34
CONTRIBUTING.md
Normal file
34
CONTRIBUTING.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Contributing
|
||||
|
||||
|
||||
♥ We love pull requests from everyone !
|
||||
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish
|
||||
to make via issue, email, or any other method with the owners of this repository
|
||||
before making a change.
|
||||
|
||||
## So all code changes happen through Pull Requests
|
||||
Pull requests are the best way to propose changes to the codebase. We actively
|
||||
welcome your pull requests:
|
||||
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've added code that need documentation, update the documentation.
|
||||
4. Be sure to test your modifications.
|
||||
5. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
6. Issue that pull request!
|
||||
|
||||
## 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/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
|
||||
under the same [MIT License](http://choosealicense.com/licenses/mit/) that
|
||||
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!
|
||||
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
|
||||
|
||||
41
README.md
41
README.md
@@ -9,19 +9,15 @@ Are YOU tired of typing every git command directly into the terminal, but you're
|
||||

|
||||
|
||||
## Installation
|
||||
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)
|
||||
### Homebrew
|
||||
```sh
|
||||
brew tap jesseduffield/lazygit
|
||||
brew install lazygit
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
Packages for Ubuntu 14.04 and up are available via Launchpad PPA.
|
||||
Packages for Ubuntu 16.04, 18.04 and 18.10 are available via Launchpad PPA.
|
||||
|
||||
They are built daily, straight from master branch.
|
||||
|
||||
@@ -31,6 +27,25 @@ sudo apt-get update
|
||||
sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
### Binary Release (Windows/Linux/OSX)
|
||||
You can download a binary release [here](https://github.com/jesseduffield/lazygit/releases)
|
||||
|
||||
### Go
|
||||
```sh
|
||||
go get github.com/jesseduffield/lazygit
|
||||
```
|
||||
|
||||
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). Not to be mistaked for `C:\Go\bin` (which is for Go's own binaries, not apps like 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,19 +60,17 @@ 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
|
||||
|
||||
## Contributing
|
||||
I'll find a good template for contributing and then add it to the repo (or if somebody has a suggestion please put up a PR)
|
||||
We love your input! Please check out the [contributing guide](CONTRIBUTING.md).
|
||||
|
||||
## Work in progress
|
||||
This is still a work in progress so there's still bugs to iron out and as this is my first project in Go the code could no doubt use an increase in quality, but I'll be improving on it whenever I find the time. If you have any feedback feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[submit a PR](https://github.com/jesseduffield/lazygit/pulls).
|
||||
|
||||
@@ -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,24 @@ 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")
|
||||
}
|
||||
if !file.Tracked {
|
||||
return createErrorPanel(g, "Cannot git add --patch untracked files")
|
||||
}
|
||||
gitAddPatch(g, file.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func getSelectedFile(g *gocui.Gui) (GitFile, error) {
|
||||
if len(state.GitFiles) == 0 {
|
||||
return GitFile{}, ErrNoFiles
|
||||
@@ -115,12 +133,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 +181,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 +190,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 +198,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", "--patch", 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 + "\"")
|
||||
}
|
||||
|
||||
|
||||
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