Compare commits

...

65 Commits

Author SHA1 Message Date
Jesse Duffield
7371765d47 bring back white borders for everybody 2018-08-08 22:53:24 +10:00
Jesse Duffield
d9ec6f9890 switch border color back to black for osx 2018-08-08 21:53:28 +10:00
Jesse Duffield
9858bfe4c3 use brighter panels for linux 2018-08-08 21:46:56 +10:00
Jesse Duffield
feada164d0 use file glob 2018-08-08 21:22:08 +10:00
Jesse Duffield
a1706ef2ee remove 1.7 because I'm not using it 2018-08-08 21:09:51 +10:00
Jesse Duffield
1bd3aa9708 remove version number from filename and use regex to pick files to deploy 2018-08-08 21:08:05 +10:00
Jesse Duffield
d0aaf94068 use default platforms 2018-08-08 21:04:04 +10:00
Jesse Duffield
8054d85782 add more archs to travis.yml 2018-08-08 20:59:32 +10:00
Jesse Duffield
31a8be5f32 update keybindings 2018-08-08 20:53:14 +10:00
Jesse Duffield
ca04655a09 merge master/better-file-opening into master 2018-08-08 20:50:45 +10:00
Jesse Duffield
4281cc2884 add git config check and editing ability 2018-08-08 20:45:12 +10:00
Jesse Duffield
8013f18177 add test script that reads user input for subprocess testing 2018-08-08 20:44:06 +10:00
Jesse Duffield
c965333756 "opening and editing" 2018-08-08 19:46:21 +10:00
Jesse Duffield
54408a912c merge moving keybindings to its own file 2018-08-08 19:24:33 +10:00
Jesse Duffield
3839719154 "move keybindings into its own file" 2018-08-08 19:18:41 +10:00
Jesse Duffield
e8c4bf20f6 merge master 2018-08-08 19:13:13 +10:00
Jesse Duffield
fbadbdd771 version with tag 2018-08-08 18:57:27 +10:00
Jesse Duffield
c6aee678c0 bump dep 2018-08-08 18:57:03 +10:00
Jesse Duffield
c4c27262f2 allow go 1.7 to fail 2018-08-08 18:02:18 +10:00
Jesse Duffield
a253b0b897 Merge branch 'master' of https://github.com/jesseduffield/lazygit 2018-08-08 08:59:09 +10:00
Jesse Duffield
5ca47c3f47 merge master 2018-08-08 08:58:24 +10:00
Jesse Duffield
456c593c72 Update README.md 2018-08-08 08:49:37 +10:00
Jesse Duffield
3b264806ef add version number 2018-08-08 08:32:52 +10:00
Jesse Duffield
92e75d4602 "step one towards dealing with gpgsign" 2018-08-08 08:24:24 +10:00
Jesse Duffield
7add598235 give everybody permission 2018-08-08 08:13:40 +10:00
Jesse Duffield
2853aba951 appending version number to binaries 2018-08-08 08:07:43 +10:00
Jesse Duffield
8cc444182f make binaries executable 2018-08-08 08:02:33 +10:00
Jesse Duffield
2a25ce014b use correct filenames 2018-08-08 07:56:55 +10:00
Jesse Duffield
411c02324f use gox default output 2018-08-08 07:54:46 +10:00
Jesse Duffield
2f50cbf2b8 use gox default output 2018-08-08 07:54:11 +10:00
Jesse Duffield
417cb97dc6 add file to command 2018-08-08 07:49:45 +10:00
Jesse Duffield
fb5d25c9e9 Merge branch 'master' into feature/better-file-opening 2018-08-08 07:41:55 +10:00
Jesse Duffield
f580572e70 Merge branch 'master' of https://github.com/jesseduffield/lazygit 2018-08-08 07:40:04 +10:00
Jesse Duffield
ba30e9e450 log some travis ci stuff 2018-08-08 07:39:58 +10:00
Nicolas Borboën
30d79bfa2c Merge remote-tracking branch 'upstream/master' into ponsfrilus/cli-version 2018-08-07 16:17:01 +02:00
Nicolas Borboën
4d9d2b134f Add lazygit --v version
* Recover Rev and Build Date from build args
* Moved `debuggingPointer` and `versionFlag` to `var()`
* Print version and exit in case of `-v` or `--v`
2018-08-07 16:15:23 +02:00
Nicolas Borboën
6cb0a14f33 Add build date to build cmd 2018-08-07 16:11:53 +02:00
Jesse Duffield
51d3e679bf Merge pull request #66 from ponsfrilus/patch-1
[fix] link to code of conduct
2018-08-07 23:21:22 +10:00
Jesse Duffield
bab884f1df nonrelative path to binaries 2018-08-07 23:18:18 +10:00
Jesse Duffield
1730089b09 try master branch for dep 2018-08-07 23:04:55 +10:00
Nicolas Borboën
8c823965ab [fix] link to code of conduct 2018-08-07 14:59:22 +02:00
Jesse Duffield
aa6a0bd43b Merge branch 'master' of https://github.com/jesseduffield/lazygit 2018-08-07 22:53:38 +10:00
Jesse Duffield
d781a8e650 if you had one shot 2018-08-07 22:53:29 +10:00
Jesse Duffield
4528cf95b9 fix wrapping 2018-08-07 22:48:40 +10:00
Jesse Duffield
dd6cdc02f0 Merge pull request #65 from ponsfrilus/ponsfrilus/coc-cg
Code of Conduct + Contributing guide proposal
2018-08-07 22:46:43 +10:00
Nicolas Borboën
2311675d08 It appears that markdown files are better handeled by GitHub with extensions. 2018-08-07 14:38:58 +02:00
Nicolas Borboën
cda365a93f Contributing guide proposal 2018-08-07 14:32:04 +02:00
Jesse Duffield
b567a86ee5 another day another attempt at CI 2018-08-07 22:30:06 +10:00
Nicolas Borboën
a6cb0feff1 Contributing guide added
This is a mix between
  * https://gist.github.com/PurpleBooth/b24679402957c63ec426
  *
https://github.com/thoughtbot/factory_bot_rails/blob/master/CONTRIBUTING.md
  * https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62
  * https://github.com/nayafia/contributing-template
2018-08-07 14:28:52 +02:00
Jesse Duffield
af766c848c reverting to 386 because it worked before 2018-08-07 22:27:33 +10:00
Nicolas Borboën
ade0dc447e Code of conduct added
* Based on contributor-covenant.org
2018-08-07 14:27:11 +02:00
Jesse Duffield
8aca30ee4c Merge branch 'master' of https://github.com/jesseduffield/lazygit 2018-08-07 22:26:12 +10:00
Jesse Duffield
d5a73c0166 experimenting 2018-08-07 22:26:08 +10:00
Jesse Duffield
53044e20c9 Merge pull request #63 from dawidd6/ubuntu
Set minimal ubuntu version supported by ppa
2018-08-07 22:20:45 +10:00
Jesse Duffield
59ea1491db back to amd64 2018-08-07 22:20:04 +10:00
Jesse Duffield
69f47fddc4 back to go 1.7 2018-08-07 22:17:42 +10:00
Jesse Duffield
75e1a2d62f support other go again 2018-08-07 22:14:30 +10:00
Jesse Duffield
b715236c0a get gox 2018-08-07 22:13:42 +10:00
Dawid Dziurla
6925ceeb6e set minimal ubuntu version supported by ppa 2018-08-07 13:42:39 +02:00
Jesse Duffield
5a624764a8 support patched adding 2018-08-07 19:50:35 +10:00
Jesse Duffield
b541987007 fixup 2018-08-07 18:37:48 +10:00
Jesse Duffield
2d4801c39d merge updated keybinding structure 2018-08-07 18:14:49 +10:00
Jesse Duffield
7fe54e889d merge master 2018-08-07 18:13:45 +10:00
Jesse Duffield
f6a9c727fa run subprocess cleanly 2018-08-07 18:05:43 +10:00
Jesse Duffield
9067c3be3e handling file edit 2018-08-06 23:29:00 +10:00
14 changed files with 518 additions and 131 deletions

View File

@@ -4,34 +4,35 @@ env:
- DEP_VERSION="0.5.0"
matrix:
include:
- go: 1.7
- go: 1.x
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
# - curl -L -s https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-386 -o $GOPATH/bin/dep
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
# 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
- if [ "${LATEST}" = "true" ]; then gox -ldflags "-X main.Rev=`git rev-parse --short HEAD` -X main.builddate=`date -u +%Y%m%d.%H%M%S` -X main.Version=${TRAVIS_TAG}" -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_glob: true
file:
- lazygit.windows.amd64.exe
- lazygit.darwin.amd64
- lazygit.linux.amd64
- lazygit_*
on:
repo: jesseduffield/lazygit
tags: true

74
CODE-OF-CONDUCT.md Normal file
View 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
View 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
View File

@@ -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

View File

@@ -9,19 +9,23 @@ Are YOU tired of typing every git command directly into the terminal, but you're
![Gif](https://image.ibb.co/mmeXho/optimisedgif.gif)
## 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 14.04 and up are available via Launchpad PPA.
Packages for Ubuntu 16.04 and up are available via Launchpad PPA.
They are built daily, straight from master branch.
@@ -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,19 +56,17 @@ sudo apt-get install lazygit
### Viewing commit diffs
![Viewing Commit Diffs](https://image.ibb.co/gPD02o/capture.png)
## 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).

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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 + "\"")
}

94
gui.go
View File

@@ -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
View 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
}

43
main.go
View File

@@ -1,19 +1,34 @@
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
// Rev - Git Revision
Rev string
// Version - Version number
Version = "unversioned"
builddate string
debuggingFlag = flag.Bool("debug", false, "a boolean")
versionFlag = flag.Bool("v", false, "Print the current version")
)
func homeDirectory() string {
@@ -37,7 +52,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 +73,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("rev=%s, build date=%s, version=%s", Rev, builddate, 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)
}
}
}
}

View 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
View 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
View 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")
}