mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-03 00:57:52 +02:00
merge master/better-file-opening into master
This commit is contained in:
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@ -57,6 +57,14 @@
|
|||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:cd5ffc5bda4e0296ab3e4de90dbb415259c78e45e7fab13694b14cde8ab74541"
|
||||||
|
name = "github.com/tcnksm/go-gitconfig"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "NUT"
|
||||||
|
revision = "d154598bacbf4501c095a309753c5d4af66caa81"
|
||||||
|
version = "v0.1.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:4d8a79fbc6fa348fc94afa4235947c5196b7900ed71b94aa5fcbc7e273d150e1"
|
digest = "1:4d8a79fbc6fa348fc94afa4235947c5196b7900ed71b94aa5fcbc7e273d150e1"
|
||||||
@ -72,6 +80,7 @@
|
|||||||
"github.com/fatih/color",
|
"github.com/fatih/color",
|
||||||
"github.com/golang-collections/collections/stack",
|
"github.com/golang-collections/collections/stack",
|
||||||
"github.com/jesseduffield/gocui",
|
"github.com/jesseduffield/gocui",
|
||||||
|
"github.com/tcnksm/go-gitconfig",
|
||||||
]
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -63,6 +63,21 @@ func handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return handleFileSelect(g, v)
|
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) {
|
func getSelectedFile(g *gocui.Gui) (GitFile, error) {
|
||||||
if len(state.GitFiles) == 0 {
|
if len(state.GitFiles) == 0 {
|
||||||
return GitFile{}, ErrNoFiles
|
return GitFile{}, ErrNoFiles
|
||||||
@ -163,7 +178,7 @@ func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
|||||||
if message == "" {
|
if message == "" {
|
||||||
return createErrorPanel(g, "You cannot commit without a commit 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)
|
return createErrorPanel(g, output)
|
||||||
}
|
}
|
||||||
refreshFiles(g)
|
refreshFiles(g)
|
||||||
@ -172,7 +187,7 @@ func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
|||||||
return nil
|
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)
|
file, err := getSelectedFile(g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrNoFiles {
|
if err != ErrNoFiles {
|
||||||
@ -180,18 +195,24 @@ func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (string, err
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if output, err := open(file.Name); err != nil {
|
if _, err := open(g, file.Name); err != nil {
|
||||||
return createErrorPanel(g, output)
|
return createErrorPanel(g, err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
func handleFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||||
return genericFileOpen(g, v, openFile)
|
return genericFileOpen(g, v, openFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||||
return genericFileOpen(g, v, sublimeOpenFile)
|
return genericFileOpen(g, v, sublimeOpenFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||||
return genericFileOpen(g, v, vsCodeOpenFile)
|
return genericFileOpen(g, v, vsCodeOpenFile)
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
gitconfig "github.com/tcnksm/go-gitconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNoCheckedOutBranch : When we have no checked out branch
|
// ErrNoCheckedOutBranch : When we have no checked out branch
|
||||||
ErrNoCheckedOutBranch = errors.New("No currently 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
|
// GitFile : A staged/unstaged file
|
||||||
@ -263,23 +268,73 @@ func runCommand(command string) (string, error) {
|
|||||||
commandStartTime := time.Now()
|
commandStartTime := time.Now()
|
||||||
commandLog(command)
|
commandLog(command)
|
||||||
splitCmd := strings.Split(command, " ")
|
splitCmd := strings.Split(command, " ")
|
||||||
|
devLog(splitCmd)
|
||||||
cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput()
|
cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput()
|
||||||
devLog("run command time: ", time.Now().Sub(commandStartTime))
|
devLog("run command time: ", time.Now().Sub(commandStartTime))
|
||||||
return sanitisedCommandOutput(cmdOut, err)
|
return sanitisedCommandOutput(cmdOut, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openFile(filename string) (string, error) {
|
func vsCodeOpenFile(g *gocui.Gui, filename string) (string, error) {
|
||||||
return runCommand("open " + filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
func vsCodeOpenFile(filename string) (string, error) {
|
|
||||||
return runCommand("code -r " + filename)
|
return runCommand("code -r " + filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sublimeOpenFile(filename string) (string, error) {
|
func sublimeOpenFile(g *gocui.Gui, filename string) (string, error) {
|
||||||
return runCommand("subl " + filename)
|
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) {
|
func getBranchGraph(branch string, baseBranch string) (string, error) {
|
||||||
return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch)
|
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
|
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 + "\"")
|
return runDirectCommand("git commit -m \"" + message + "\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
gui.go
14
gui.go
@ -5,7 +5,6 @@ import (
|
|||||||
// "io"
|
// "io"
|
||||||
// "io/ioutil"
|
// "io/ioutil"
|
||||||
|
|
||||||
"log"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -207,10 +206,10 @@ func updateLoader(g *gocui.Gui) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
func run() (err error) {
|
||||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
return
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
@ -229,13 +228,12 @@ func run() {
|
|||||||
|
|
||||||
g.SetManagerFunc(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := keybindings(g); err != nil {
|
if err = keybindings(g); err != nil {
|
||||||
log.Panicln(err)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
err = g.MainLoop()
|
||||||
log.Panicln(err)
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
@ -30,6 +30,7 @@ func keybindings(g *gocui.Gui) error {
|
|||||||
Binding{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
|
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: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
|
||||||
Binding{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
|
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: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
|
||||||
Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
|
Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
|
||||||
Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
|
Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
|
||||||
@ -37,6 +38,7 @@ func keybindings(g *gocui.Gui) error {
|
|||||||
Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
|
Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
|
||||||
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
|
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
|
||||||
Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
|
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.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
|
||||||
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
|
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.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
|
||||||
|
24
main.go
24
main.go
@ -1,19 +1,24 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrSubProcess is raised when we are running a subprocess
|
||||||
var (
|
var (
|
||||||
|
ErrSubprocess = errors.New("running subprocess")
|
||||||
|
subprocess *exec.Cmd
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
debugging bool
|
|
||||||
|
|
||||||
// Rev - Git Revision
|
// Rev - Git Revision
|
||||||
Rev string
|
Rev string
|
||||||
@ -22,7 +27,7 @@ var (
|
|||||||
Version = "unversioned"
|
Version = "unversioned"
|
||||||
|
|
||||||
builddate string
|
builddate string
|
||||||
debuggingPointer = flag.Bool("debug", false, "a boolean")
|
debuggingFlag = flag.Bool("debug", false, "a boolean")
|
||||||
versionFlag = flag.Bool("v", false, "Print the current version")
|
versionFlag = flag.Bool("v", false, "Print the current version")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,7 +52,7 @@ func commandLog(objects ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func localLog(colour color.Attribute, path string, objects ...interface{}) {
|
func localLog(colour color.Attribute, path string, objects ...interface{}) {
|
||||||
if !debugging {
|
if !*debuggingFlag {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
|
f, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
@ -69,7 +74,6 @@ func navigateToRepoRootDirectory() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
debugging = *debuggingPointer
|
|
||||||
devLog("\n\n\n\n\n\n\n\n\n\n")
|
devLog("\n\n\n\n\n\n\n\n\n\n")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *versionFlag {
|
if *versionFlag {
|
||||||
@ -78,5 +82,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
verifyInGitRepo()
|
verifyInGitRepo()
|
||||||
navigateToRepoRootDirectory()
|
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